Objective-C 快速指南



Objective-C 概述

Objective-C 是一種通用語言,它在 C 程式語言的基礎上發展而來,並添加了 Small Talk 程式語言的功能,使其成為一種面向物件的語言。它主要用於開發 iOS 和 Mac OS X 作業系統及其應用程式。

最初,Objective-C 由 NeXT 為其 NeXTSTEP 作業系統開發,之後被 Apple 收購,用於其 iOS 和 Mac OS X 系統。

面向物件程式設計

完全支援面向物件程式設計,包括面向物件開發的四大支柱:

  • 封裝
  • 資料隱藏
  • 繼承
  • 多型

示例程式碼

#import <Foundation/Foundation.h>

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

   NSLog (@"hello world");
   [pool drain];
   return 0;
}

Foundation 框架

Foundation 框架提供了大量的功能,列舉如下:

  • 它包含一系列擴充套件的資料型別,例如 NSArray、NSDictionary、NSSet 等。

  • 它包含一組豐富的用於操作檔案、字串等的函式。

  • 它提供 URL 處理、日期格式化、資料處理、錯誤處理等實用程式功能。

學習 Objective-C

學習 Objective-C 最重要的事情是專注於概念,不要迷失在語言的技術細節中。

學習程式語言的目的是成為一名更好的程式設計師;也就是說,在設計和實現新系統以及維護舊系統方面更有效率。

Objective-C 的用途

如前所述,Objective-C 用於 iOS 和 Mac OS X。它擁有龐大的 iOS 使用者群,並且 Mac OS X 使用者也在不斷增長。由於 Apple 始終將質量放在首位,這對開始學習 Objective-C 的人來說非常棒。

Objective-C 環境設定

本地環境設定

如果您仍然希望為 Objective-C 程式語言設定環境,則需要在您的計算機上安裝以下兩個軟體:(a) 文字編輯器和 (b) GCC 編譯器。

文字編輯器

這將用於鍵入您的程式。一些編輯器的示例包括 Windows 記事本、OS Edit 命令、Brief、Epsilon、EMACS 和 vim 或 vi。

文字編輯器的名稱和版本在不同的作業系統上可能有所不同。例如,Windows 上使用記事本,而 vim 或 vi 可以在 Windows、Linux 或 UNIX 上使用。

您使用編輯器建立的檔案稱為原始檔,其中包含程式原始碼。Objective-C 程式的原始檔通常以“.m”副檔名命名。

在開始程式設計之前,請確保您已準備好一個文字編輯器,並且您有足夠的經驗來編寫計算機程式,將其儲存在檔案中,編譯它,最後執行它。

GCC 編譯器

原始檔中編寫的原始碼是您的程式的人類可讀原始碼。它需要“編譯”才能轉換為機器語言,以便您的 CPU 能夠根據給定的指令實際執行程式。

此 GCC 編譯器將用於將您的原始碼編譯成最終的可執行程式。我假設您瞭解程式語言編譯器的基本知識。

GCC 編譯器可在各種平臺上免費獲得,下面介紹了在各種平臺上進行設定的過程。

在 UNIX/Linux 上安裝

第一步是安裝 gcc 以及 gcc Objective-C 包。方法如下:

$ su - 
$ yum install gcc
$ yum install gcc-objc

下一步是使用以下命令設定包依賴項:

$ yum install make libpng libpng-devel libtiff libtiff-devel libobjc 
   libxml2 libxml2-devel libX11-devel libXt-devel libjpeg libjpeg-devel

為了獲得 Objective-C 的完整功能,請下載並安裝 GNUStep。

現在,我們需要切換到下載的資料夾並解壓縮檔案:

$ tar xvfz gnustep-startup-.tar.gz

現在,我們需要切換到使用以下命令建立的 gnustep-startup 資料夾:

$ cd gnustep-startup-<version>

接下來,我們需要配置構建過程:

$ ./configure

然後,我們可以透過以下方式構建:

$ make

最後,我們需要設定環境:

$ . /usr/GNUstep/System/Library/Makefiles/GNUstep.sh

我們有一個名為 helloWorld.m 的 Objective-C 檔案,如下所示:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   
   NSLog (@"hello world");
   [pool drain];
   return 0;
}

現在,我們可以透過使用 cd 切換到包含該檔案的資料夾,然後使用以下步驟來編譯和執行 Objective-C 檔案 helloWorld.m:

$ gcc `gnustep-config --objc-flags` 
-L/usr/GNUstep/Local/Library/Libraries 
-lgnustep-base helloWorld.m -o helloWorld
$ ./helloWorld

我們將看到以下輸出:

2013-09-07 10:48:39.772 tutorialsPoint[12906] hello world

在 Mac OS 上安裝

如果您使用 Mac OS X,獲取 GCC 的最簡單方法是從 Apple 的網站下載 Xcode 開發環境,並按照簡單的安裝說明進行操作。設定 Xcode 後,您將能夠使用 GNU C/C++ 編譯器。

Xcode 目前可在 developer.apple.com/technologies/tools/ 獲取。

在 Windows 上安裝

為了在 Windows 上執行 Objective-C 程式,我們需要安裝 MinGW 和 GNUStep Core。兩者都可在 https://www.gnu.org/software/gnustep/windows/installer.html 獲取。

首先,我們需要安裝 MSYS/MinGW 系統包。然後,我們需要安裝 GNUstep Core 包。兩者都提供了一個 Windows 安裝程式,這是不言自明的。

然後透過選擇“開始”->“所有程式”->“GNUstep”->“Shell”來使用 Objective-C 和 GNUstep。

切換到包含 helloWorld.m 的資料夾。

我們可以使用以下命令編譯程式:

$ gcc `gnustep-config --objc-flags` 
-L /GNUstep/System/Library/Libraries hello.m -o hello -lgnustep-base -lobjc

我們可以使用以下命令執行程式:

./hello.exe

我們將得到以下輸出:

2013-09-07 10:48:39.772 tutorialsPoint[1200] hello world

Objective-C 程式結構

在學習 Objective-C 程式語言的基本構建塊之前,讓我們先看看一個最小的 Objective-C 程式結構,以便我們可以在接下來的章節中將其作為參考。

Objective-C “Hello World” 示例

一個 Objective-C 程式主要由以下部分組成:

  • 預處理器命令
  • 介面
  • 實現
  • 方法
  • 變數
  • 語句和表示式
  • 註釋

讓我們來看一個簡單的程式碼,它將列印“Hello World”字樣:

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass

- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

@end

int main() {
   /* my first program in Objective-C */
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass sampleMethod];
   return 0;
}

讓我們看看上面程式的各個部分:

  • 程式的第一行#import 是一個預處理器命令,它告訴 Objective-C 編譯器在進行實際編譯之前包含 Foundation.h 檔案。

  • 下一行@interface SampleClass:NSObject顯示瞭如何建立一個介面。它繼承了 NSObject,它是所有物件的基類。

  • 下一行- (void)sampleMethod;顯示瞭如何宣告一個方法。

  • 下一行@end標誌著介面的結束。

  • 下一行@implementation SampleClass顯示瞭如何實現介面 SampleClass。

  • 下一行- (void)sampleMethod{}顯示了 sampleMethod 的實現。

  • 下一行@end標誌著實現的結束。

  • 下一行int main()是程式執行開始的主函式。

  • 下一行/*...*/將被編譯器忽略,它被用來在程式中新增額外的註釋。因此,這樣的行被稱為程式中的註釋。

  • 下一行NSLog(...)是 Objective-C 中的另一個可用函式,它會導致訊息“Hello, World!”顯示在螢幕上。

  • 下一行return 0;終止 main() 函式並返回 0 值。

編譯和執行 Objective-C 程式

現在,當我們編譯並執行程式時,我們將得到以下結果。

2017-10-06 07:48:32.020 demo[65832] Hello, World!

Objective-C 基本語法

您已經看到了 Objective-C 程式的基本結構,因此很容易理解 Objective-C 程式語言的其他基本構建塊。

Objective-C 中的標記

一個 Objective-C 程式由各種標記組成,一個標記要麼是關鍵字、識別符號、常量、字串文字或符號。例如,以下 Objective-C 語句包含六個標記:

NSLog(@"Hello, World! \n");

各個標記是:

NSLog
@
(
   "Hello, World! \n"
)
;

分號 ;

在 Objective-C 程式中,分號是語句終止符。也就是說,每個語句都必須以分號結尾。它表示一個邏輯實體的結束。

例如,以下是兩個不同的語句:

NSLog(@"Hello, World! \n");
return 0;

註釋

註釋就像 Objective-C 程式中的幫助文字,它們會被編譯器忽略。它們以/*開頭,以字元*/結尾,如下所示:

/* my first program in Objective-C */

您不能在註釋中巢狀註釋,並且它們不會出現在字串或字元文字中。

識別符號

Objective-C 識別符號是用於標識變數、函式或任何其他使用者定義項的名稱。識別符號以字母 A 到 Z 或 a 到 z 或下劃線 _ 開頭,後跟零個或多個字母、下劃線和數字 (0 到 9)。

Objective-C 不允許在識別符號中使用@、$和%等標點符號。Objective-C 是一種區分大小寫的程式語言。因此,Manpowermanpower 在 Objective-C 中是兩個不同的識別符號。以下是一些可接受的識別符號示例:

mohd       zara    abc   move_name  a_123
myname50   _temp   j     a23b9      retVal

關鍵字

以下列表顯示了 Objective-C 中的一些保留字。這些保留字不能用作常量、變數或任何其他識別符號名稱。

auto else long switch
break enum register typedef
case extern return union
char float short unsigned
const for signed void
continue goto sizeof volatile
default if static while
do int struct _Packed
double protocol interface implementation
NSObject NSInteger NSNumber CGFloat
property nonatomic; retain strong
weak unsafe_unretained; readwrite readonly

Objective-C 中的空格

僅包含空格(可能還有註釋)的行稱為空行,Objective-C 編譯器會完全忽略它。

在 Objective-C 中,“空格”指的是空格符、製表符、換行符和註釋。空格將語句的一個部分與另一個部分隔開,使編譯器能夠識別語句中一個元素(例如 int)的結束位置和下一個元素的開始位置。因此,在以下語句中:

int age;

int 和 age 之間必須至少有一個空格字元(通常是空格),以便編譯器能夠區分它們。另一方面,在以下語句中:

fruit = apples + oranges;   // get the total fruit

fruit 和 = 之間,以及 = 和 apples 之間不需要空格字元,儘管為了可讀性,您可以隨意新增一些空格。

Objective-C 資料型別

在 Objective-C 程式語言中,資料型別指的是一個廣泛的系統,用於宣告不同型別的變數或函式。變數的型別決定了它在儲存中佔用的空間大小以及如何解釋儲存的位模式。

Objective-C 中的型別可以分類如下:

序號 型別和描述
1

基本型別:

它們是算術型別,包括兩種型別:(a)整數型別和(b)浮點型別。

2

列舉型別:

它們也是算術型別,用於定義只能在整個程式中分配某些離散整數值的變數。

3

void 型別:

型別說明符 void 表示沒有可用值。

4

派生型別:

它們包括 (a) 指標型別,(b) 陣列型別,(c) 結構型別,(d) 聯合型別和 (e) 函式型別。

陣列型別和結構型別統稱為聚合型別。函式的型別指定函式返回值的型別。我們將在下一節中介紹基本型別,其他型別將在後續章節中介紹。

整數型別

下表提供了標準整數型別及其儲存大小和值範圍的詳細資訊:

型別 儲存大小 值範圍
char 1 位元組 -128 到 127 或 0 到 255
unsigned char 1 位元組 0 到 255
signed char 1 位元組 -128 到 127
int 2 或 4 位元組 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2 或 4 位元組 0 到 65,535 或 0 到 4,294,967,295
short 2 位元組 -32,768 到 32,767
unsigned short 2 位元組 0 到 65,535
long 4 位元組 -2,147,483,648 到 2,147,483,647
unsigned long 4 位元組 0 到 4,294,967,295

要在特定平臺上獲取型別或變數的確切大小,可以使用sizeof運算子。表示式 sizeof(type) 以位元組為單位生成物件或型別的儲存大小。以下是如何獲取任何機器上 int 型別大小的示例:

#import <Foundation/Foundation.h>

int main() {
   NSLog(@"Storage size for int : %d \n", sizeof(int));
   return 0;
}

編譯並執行上述程式時,它在 Linux 上會產生以下結果:

2013-09-07 22:21:39.155 demo[1340] Storage size for int : 4 

浮點型別

下表提供了標準浮點型別及其儲存大小、值範圍和精度的詳細資訊:

型別 儲存大小 值範圍 精度
float 4 位元組 1.2E-38 到 3.4E+38 6 位小數
double 8 位元組 2.3E-308 到 1.7E+308 15 位小數
long double 10 位元組 3.4E-4932 到 1.1E+4932 19 位小數

標頭檔案 float.h 定義了宏,允許您在程式中使用這些值以及有關實數二進位制表示的其他詳細資訊。以下示例將列印 float 型別佔用的儲存空間及其範圍值:

#import <Foundation/Foundation.h>

int main() {
   NSLog(@"Storage size for float : %d \n", sizeof(float));
   return 0;
}

編譯並執行上述程式時,它在 Linux 上會產生以下結果:

2013-09-07 22:22:21.729 demo[3927] Storage size for float : 4 

void 型別

void 型別指定沒有可用值。它用於三種情況:

序號 型別和描述
1 函式返回值為 void

Objective-C 中有許多函式不返回值,或者可以說它們返回 void。沒有返回值的函式的返回型別為 void。例如,void exit (int status);

2 函式引數為 void

Objective-C 中有許多函式不接受任何引數。沒有引數的函式可以接受 void。例如,int rand(void);

此時您可能還不理解 void 型別,因此讓我們繼續,我們將在後續章節中介紹這些概念。

Objective-C 變數

變數只不過是賦予儲存區域的名稱,我們的程式可以操作它。Objective-C 中的每個變數都有一個特定的型別,它決定變數記憶體的大小和佈局;可以儲存在該記憶體中的值的範圍;以及可以應用於變數的操作集。

變數名可以由字母、數字和下劃線字元組成。它必須以字母或下劃線開頭。由於 Objective-C 區分大小寫,因此大寫字母和小寫字母是不同的。基於上一章解釋的基本型別,將有以下基本變數型別:

序號 型別和描述
1

char

通常是一個單一 octet(一個位元組)。這是一個整數型別。

2

int

機器最自然的整數大小。

3

float

單精度浮點值。

4

double

雙精度浮點值。

5

void

表示型別不存在。

Objective-C 程式語言還允許定義各種其他型別的變數,我們將在後續章節中介紹,例如列舉、指標、陣列、結構、聯合等。在本節中,讓我們只學習基本變數型別。

Objective-C 中的變數定義

變數定義意味著告訴編譯器在哪裡以及為變數建立多少儲存空間。變數定義指定資料型別,幷包含該型別的一個或多個變數列表,如下所示:

type variable_list;

這裡,type 必須是有效的 Objective-C 資料型別,包括 char、w_char、int、float、double、bool 或任何使用者定義的物件等,而 variable_list 可以包含一個或多個以逗號分隔的識別符號名稱。此處顯示了一些有效的宣告:

int    i, j, k;
char   c, ch;
float  f, salary;
double d;

語句 int i, j, k; 同時宣告並定義了變數 i、j 和 k;它指示編譯器建立名為 i、j 和 k 的 int 型別變數。

可以在變數宣告中初始化(分配初始值)變數。初始化程式由等號後跟一個常量表達式組成,如下所示:

type variable_name = value;

以下是一些示例:

extern int d = 3, f = 5;    // declaration of d and f. 
int d = 3, f = 5;           // definition and initializing d and f. 
byte z = 22;                // definition and initializes z. 
char x = 'x';               // the variable x has the value 'x'.

對於沒有初始化程式的定義:具有靜態儲存持續時間的變數隱式初始化為 NULL(所有位元組的值均為 0);所有其他變數的初始值未定義。

Objective-C 中的變數宣告

變數宣告向編譯器保證存在一個具有給定型別和名稱的變數,以便編譯器在無需瞭解有關變數的完整詳細資訊的情況下繼續進行編譯。變數宣告僅在編譯時有效,編譯器在程式連結時需要實際的變數宣告。

當您使用多個檔案並在其中一個檔案中定義變數時,變數宣告很有用,該變數將在程式連結時可用。您將使用extern關鍵字在任何地方宣告變數。儘管您可以在 Objective-C 程式中多次宣告變數,但它在一個檔案、一個函式或一段程式碼中只能定義一次。

示例

嘗試以下示例,其中變數已在頂部宣告,但它們已在 main 函式內定義和初始化:

#import <Foundation/Foundation.h>

// Variable declaration:
extern int a, b;
extern int c;
extern float f;

int main () {
  /* variable definition: */
  int a, b;
  int c;
  float f;
 
  /* actual initialization */
  a = 10;
  b = 20;
  
  c = a + b;
  NSLog(@"value of c : %d \n", c);

  f = 70.0/3.0;
  NSLog(@"value of f : %f \n", f);
 
  return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-07 22:43:31.695 demo[14019] value of c : 30 
2013-09-07 22:43:31.695 demo[14019] value of f : 23.333334 

相同的概念也適用於函式宣告,您在函式宣告時提供函式名稱,其實際定義可以在其他任何地方給出。在下面的示例中,它使用 C 函式進行解釋,並且您知道 Objective-C 也支援 C 風格的函式:

// function declaration
int func();

int main() {
   // function call
   int i = func();
}

// function definition
int func() {
   return 0;
}

Objective-C 中的左值和右值

Objective-C 中有兩種表示式:

  • 左值:引用記憶體位置的表示式稱為“左值”表示式。左值可以作為賦值的左側或右側出現。

  • 右值:術語右值指的是儲存在記憶體中某個地址的資料值。右值是一個不能為其賦值的表示式,這意味著右值可以出現在賦值的右側,但不能出現在左側。

變數是左值,因此可以出現在賦值的左側。數字文字是右值,因此不能賦值,也不能出現在左側。以下是一個有效的語句:

int g = 20;

但是以下語句無效,會生成編譯時錯誤:

10 = 20;

Objective-C 常量

常量指的是程式在其執行過程中可能不會更改的固定值。這些固定值也稱為文字

常量可以是任何基本資料型別,例如整數常量、浮點常量、字元常量或字串文字。還有列舉常量。

常量的處理方式與常規變數相同,只是它們的定義後其值不能修改。

整數文字

整數文字可以是十進位制、八進位制或十六進位制常量。字首指定基數或基:十六進位制為 0x 或 0X,八進位制為 0,十進位制為無。

整數文字還可以有一個字尾,它是 U 和 L 的組合,分別表示無符號和長整型。字尾可以是大寫或小寫,並且可以按任何順序排列。

以下是一些整數文字的示例:

212         /* Legal */
215u        /* Legal */
0xFeeL      /* Legal */
078         /* Illegal: 8 is not an octal digit */
032UU       /* Illegal: cannot repeat a suffix */

以下是各種型別整數文字的其他示例:

85         /* decimal */
0213       /* octal */
0x4b       /* hexadecimal */
30         /* int */
30u        /* unsigned int */
30l        /* long */
30ul       /* unsigned long */

浮點文字

浮點文字具有整數部分、小數點、小數部分和指數部分。您可以使用十進位制形式或指數形式表示浮點文字。

使用十進位制形式表示時,必須包含小數點、指數或兩者;使用指數形式表示時,必須包含整數部分、小數部分或兩者。帶符號的指數由 e 或 E 引入。

以下是浮點字面量的一些示例:

3.14159       /* Legal */
314159E-5L    /* Legal */
510E          /* Illegal: incomplete exponent */
210f          /* Illegal: no decimal or exponent */
.e55          /* Illegal: missing integer or fraction */

字元常量

字元字面量用單引號括起來,例如 'x',可以儲存在char型別的簡單變數中。

字元字面量可以是普通字元(例如 'x')、轉義序列(例如 '\t')或通用字元(例如 '\u02C0')。

在 C 語言中,某些字元在前面加上反斜槓後將具有特殊含義,用於表示換行符 (\n) 或製表符 (\t) 等。以下是一些轉義序列程式碼的列表:

轉義序列 含義
\\ \ 字元
\' ' 字元
\" " 字元
\? ? 字元
\a 警告或鈴聲
\b 退格
\f 換頁
\n 換行
\r 回車
\t 水平製表符
\v 垂直製表符
\ooo 一位到三位八進位制數
\xhh . . . 一位或多位十六進位制數

以下示例演示了一些轉義序列字元:

#import <Foundation/Foundation.h>

int main() {
   NSLog(@"Hello\tWorld\n\n");
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-07 22:17:17.923 demo[17871] Hello	World

字串字面量

字串字面量或常量用雙引號 "" 括起來。字串包含與字元字面量相似的字元:普通字元、轉義序列和通用字元。

可以使用字串字面量將長行分成多行,並用空格分隔它們。

以下是一些字串字面量的示例。所有三種形式都是相同的字串。

"hello, dear"

"hello, \

dear"

"hello, " "d" "ear"

定義常量

在 C 語言中,定義常量有兩種簡單的方法:

  • 使用#define預處理器。

  • 使用const關鍵字。

#define 預處理器

以下是使用 #define 預處理器定義常量的形式:

#define identifier value

以下示例詳細說明了它:

#import <Foundation/Foundation.h>

#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'

int main() {
   int area;
   area = LENGTH * WIDTH;
   NSLog(@"value of area : %d", area);
   NSLog(@"%c", NEWLINE);

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-07 22:18:16.637 demo[21460] value of area : 50
2013-09-07 22:18:16.638 demo[21460] 

const 關鍵字

可以使用const字首宣告具有特定型別的常量,如下所示:

const type variable = value;

以下示例詳細說明了它:

#import <Foundation/Foundation.h>

int main() {
   const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
   int area;  
   
   area = LENGTH * WIDTH;
   NSLog(@"value of area : %d", area);
   NSLog(@"%c", NEWLINE);

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-07 22:19:24.780 demo[25621] value of area : 50
2013-09-07 22:19:24.781 demo[25621] 

注意,良好的程式設計習慣是將常量定義為大寫字母。

Objective-C 運算子

運算子是一個符號,它告訴編譯器執行特定的數學或邏輯操作。Objective-C 語言富含內建運算子,並提供以下型別的運算子:

  • 算術運算子
  • 關係運算符
  • 邏輯運算子
  • 位運算子
  • 賦值運算子
  • 其他運算子

本教程將逐一解釋算術、關係、邏輯、位、賦值和其他運算子。

算術運算子

下表顯示了 Objective-C 語言支援的所有算術運算子。假設變數A的值為 10,變數B的值為 20,則:

示例

運算子 描述 示例
+ 將兩個運算元相加 A + B 的結果為 30
- 從第一個運算元中減去第二個運算元 A - B 的結果為 -10
* 將兩個運算元相乘 A * B 的結果為 200
/ 將分子除以分母 B / A 的結果為 2
% 模運算子,整數除法後的餘數 B % A 的結果為 0
++ 增量運算子將整數值增加一 A++ 的結果為 11
-- 減量運算子將整數值減少一 A-- 的結果為 9

關係運算符

下表顯示了 Objective-C 語言支援的所有關係運算符。假設變數A的值為 10,變數B的值為 20,則:

示例

運算子 描述 示例
== 檢查兩個運算元的值是否相等;如果相等,則條件為真。 (A == B) 為假。
!= 檢查兩個運算元的值是否相等;如果不相等,則條件為真。 (A != B) 為真。
> 檢查左運算元的值是否大於右運算元的值;如果大於,則條件為真。 (A > B) 為假。
< 檢查左運算元的值是否小於右運算元的值;如果小於,則條件為真。 (A < B) 為真。
>= 檢查左運算元的值是否大於或等於右運算元的值;如果大於或等於,則條件為真。 (A >= B) 為假。
<= 檢查左運算元的值是否小於或等於右運算元的值;如果小於或等於,則條件為真。 (A <= B) 為真。

邏輯運算子

下表顯示了 Objective-C 語言支援的所有邏輯運算子。假設變數A的值為 1,變數B的值為 0,則:

示例

運算子 描述 示例
&& 稱為邏輯與運算子。如果兩個運算元均非零,則條件為真。 (A && B) 為假。
|| 稱為邏輯或運算子。如果兩個運算元中任何一個非零,則條件為真。 (A || B) 為真。
! 稱為邏輯非運算子。用於反轉其運算元的邏輯狀態。如果條件為真,則邏輯非運算子將使其為假。 !(A && B) 為真。

位運算子

位運算子對位進行操作,並執行逐位運算。&,| 和 ^ 的真值表如下:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假設 A = 60;B = 13;現在以二進位制格式,它們將如下所示:

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A = 1100 0011

Objective-C 語言支援的位運算子列在下表中。假設變數 A 為 60,變數 B 為 13,則:

示例

運算子 描述 示例
& 二進位制與運算子:如果位同時存在於兩個運算元中,則將其複製到結果中。 (A & B) 的結果為 12,即 0000 1100
| 二進位制或運算子:如果位存在於任何一個運算元中,則將其複製。 (A | B) 的結果為 61,即 0011 1101
^ 二進位制異或運算子:如果位在一個運算元中設定,但在另一個運算元中未設定,則將其複製。 (A ^ B) 的結果為 49,即 0011 0001
~ 二進位制非運算子:是單目運算子,作用是“翻轉”位。 (~A) 的結果為 -61,在二進位制補碼形式中為 1100 0011。
<< 二進位制左移運算子:左運算元的值向左移動由右運算元指定的位數。 A << 2 的結果為 240,即 1111 0000
>> 二進位制右移運算子:左運算元的值向右移動由右運算元指定的位數。 A >> 2 的結果為 15,即 0000 1111

賦值運算子

Objective-C 語言支援以下賦值運算子:

示例

運算子 描述 示例
= 簡單賦值運算子,將值從右運算元賦值給左運算元 C = A + B 將 A + B 的值賦給 C
+= 加法賦值運算子,它將右運算元新增到左運算元,並將結果賦值給左運算元 C += A 等效於 C = C + A
-= 減法賦值運算子,它從左運算元中減去右運算元,並將結果賦值給左運算元 C -= A 等效於 C = C - A
*= 乘法賦值運算子,它將右運算元與左運算元相乘,並將結果賦值給左運算元 C *= A 等效於 C = C * A
/= 除法賦值運算子,它將左運算元除以右運算元,並將結果賦值給左運算元 C /= A 等效於 C = C / A
%= 模賦值運算子,它使用兩個運算元取模,並將結果賦值給左運算元 C %= A 等效於 C = C % A
<<= 左移賦值運算子 C <<= 2 與 C = C << 2 相同
>>= 右移賦值運算子 C >>= 2 與 C = C >> 2 相同
&= 位與賦值運算子 C &= 2 與 C = C & 2 相同
^= 位異或賦值運算子 C ^= 2 與 C = C ^ 2 相同
|= 位或賦值運算子 C |= 2 與 C = C | 2 相同

其他運算子 ↦ sizeof & 三元運算子

Objective-C 語言還支援其他一些重要的運算子,包括sizeof?:

示例

運算子 描述 示例
sizeof() 返回變數的大小。 sizeof(a),其中 a 是整數,將返回 4。
& 返回變數的地址。 &a; 將給出變數的實際地址。
* 指向變數的指標。 *a; 將指向一個變數。
? : 條件表示式 如果條件為真?則值為 X:否則值為 Y

Objective-C 中的運算子優先順序

運算子優先順序決定了表示式中項的組合方式。這會影響表示式的計算方式。某些運算子的優先順序高於其他運算子;例如,乘法運算子的優先順序高於加法運算子:

例如,x = 7 + 3 * 2;這裡,x 被賦值為 13,而不是 20,因為運算子 * 的優先順序高於 +,所以它首先與 3*2 相乘,然後加到 7 中。

這裡,優先順序最高的運算子出現在表格的頂部,優先順序最低的出現在底部。在表示式中,將首先計算優先順序較高的運算子。

類別 運算子 結合性
字尾 () [] -> . ++ -- 從左到右
一元 + - ! ~ ++ -- (type)* & sizeof 從右到左
乘法 * / % 從左到右
加法 + - 從左到右
移位 << >> 從左到右
關係 < <= > >= 從左到右
相等 == != 從左到右
位異或 ^ 從左到右
位或 | 從左到右
邏輯與 && 從左到右
邏輯或 || 從左到右
條件 ?: 從右到左
賦值 = += -= *= /= %= >>= <<= &= ^= |= 從右到左
逗號 , 從左到右

Objective-C 迴圈

可能會有這種情況,您需要多次執行一段程式碼。通常,語句是順序執行的:函式中的第一個語句首先執行,然後是第二個,依此類推。

程式語言提供各種控制結構,允許更復雜的執行路徑。

迴圈語句允許我們多次執行語句或語句組,以下是在大多數程式語言中迴圈語句的一般形式:

Loop Architecture

Objective-C 程式語言提供以下型別的迴圈來處理迴圈需求。點選以下連結檢視其詳細資訊。

序號 迴圈型別和描述
1 while 迴圈

在給定條件為真時重複執行語句或語句組。它在執行迴圈體之前測試條件。

2 for 迴圈

多次執行一系列語句,並縮寫管理迴圈變數的程式碼。

3 do...while 迴圈

類似於 while 語句,只是它在迴圈體末尾測試條件。

4 巢狀迴圈

可以在任何其他的 while、for 或 do..while 迴圈內使用一個或多個迴圈。

迴圈控制語句

迴圈控制語句改變執行的正常順序。當執行離開一個作用域時,在該作用域中建立的所有自動物件都會被銷燬。

Objective-C 支援以下控制語句。點選以下連結檢視它們的詳細資訊。

序號 控制語句 & 說明
1 break 語句

終止迴圈switch語句,並將執行轉移到迴圈或switch之後緊跟的語句。

2 continue 語句

使迴圈跳過其主體其餘部分,並在立即重新測試其條件之前立即重複。

無限迴圈

如果條件永不為假,則迴圈將變成無限迴圈。for迴圈傳統上用於此目的。由於構成for迴圈的三個表示式都不是必需的,因此您可以透過將條件表示式留空來建立一個無限迴圈。

#import <Foundation/Foundation.h>
 
int main () {

   for( ; ; ) {
      NSLog(@"This loop will run forever.\n");
   }

   return 0;
}

如果條件表示式不存在,則假定其為真。您可能有一個初始化和增量表達式,但是 Objective-C 程式設計師更常用 for(;;) 結構來表示無限迴圈。

Objective-C 決策

決策結構要求程式設計師指定一個或多個要由程式評估或測試的條件,以及如果確定條件為真則要執行的語句(或語句),以及可選地,如果確定條件為假則要執行的其他語句。

以下是大多數程式語言中常見的典型決策結構的一般形式:

Decision making statements in Objective-C

Objective-C 程式語言將任何非零非空值視為,如果它是,則將其視為值。

Objective-C 程式語言提供以下型別的決策語句。點選以下連結檢視它們的詳細資訊:

序號 語句 & 說明
1 if 語句

if 語句由一個布林表示式後跟一個或多個語句組成。

2 if...else 語句

if 語句後可以跟一個可選的else 語句,當布林表示式為假時執行。

3 巢狀 if 語句

可以在另一個ifelse if語句內使用一個ifelse if語句。

4 switch 語句

switch語句允許測試變數與值的列表是否相等。

5 巢狀 switch 語句

可以在另一個switch語句內使用一個switch語句。

?: 運算子

我們在上一章中介紹了條件運算子 ?:,它可以用來代替if...else語句。它具有以下一般形式:

Exp1 ? Exp2 : Exp3;

其中 Exp1、Exp2 和 Exp3 是表示式。注意冒號的使用和位置。

?: 表示式的值如下確定:評估 Exp1。如果為真,則評估 Exp2 併成為整個 ?: 表示式的值。如果 Exp1 為假,則評估 Exp3,其值成為表示式的值。

Objective-C 函式

函式是一組共同執行任務的語句。每個 Objective-C 程式都有一個 C 函式,即main(),並且所有最簡單的程式都可以定義附加函式。

您可以將程式碼分成單獨的函式。您如何將程式碼劃分到不同的函式中取決於您自己,但邏輯上,劃分通常是每個函式執行特定任務。

函式宣告告訴編譯器有關函式的名稱、返回型別和引數的資訊。函式定義提供了函式的實際主體。

基本上在 Objective-C 中,我們稱函式為方法。

Objective-C 基礎框架提供了許多內建方法,您的程式可以呼叫這些方法。例如,方法appendString()將字串附加到另一個字串。

方法以各種名稱而聞名,例如函式、子例程或過程等。

定義方法

Objective-C 程式語言中方法定義的一般形式如下:

- (return_type) method_name:( argumentType1 )argumentName1 
joiningArgument2:( argumentType2 )argumentName2 ... 
joiningArgumentn:( argumentTypen )argumentNamen {
   body of the function
}

Objective-C 程式語言中的方法定義由方法頭方法體組成。以下是方法的所有部分:

  • 返回型別 - 方法可以返回值。return_type 是函式返回的值的資料型別。某些方法執行所需的操作而不返回值。在這種情況下,return_type 是關鍵字void

  • 方法名稱 - 這是方法的實際名稱。方法名稱和引數列表共同構成方法簽名。

  • 引數 - 引數就像一個佔位符。呼叫函式時,您將值傳遞給引數。此值稱為實際引數或引數。引數列表是指方法的引數的型別、順序和數量。引數是可選的;也就是說,方法可能不包含任何引數。

  • 連線引數 - 連線引數是為了使其更易於閱讀並在呼叫時使其更清晰。

  • 方法體 - 方法體包含定義方法作用的一組語句。

示例

以下是名為max()的方法的原始碼。此方法接受兩個引數 num1 和 num2,並返回兩者之間的最大值:

/* function returning the max between two numbers */
- (int) max:(int) num1 secondNumber:(int) num2 {
   
   /* local variable declaration */
   int result;
 
   if (num1 > num2) {
      result = num1;
   } else {
      result = num2;
   }
 
   return result; 
}

方法宣告

方法宣告告訴編譯器有關函式名稱以及如何呼叫方法的資訊。函式的實際主體可以單獨定義。

方法宣告包含以下部分:

- (return_type) function_name:( argumentType1 )argumentName1 
joiningArgument2:( argumentType2 )argumentName2 ... 
joiningArgumentn:( argumentTypen )argumentNamen;

對於上面定義的函式 max(),以下是方法宣告:

-(int) max:(int)num1 andNum2:(int)num2;

當您在一個原始檔中定義方法並在另一個檔案中呼叫該方法時,需要方法宣告。在這種情況下,您應該在呼叫函式的檔案頂部宣告該函式。

呼叫方法

建立 Objective-C 方法時,您將定義函式必須執行的操作。要使用方法,您必須呼叫該函式以執行已定義的任務。

當程式呼叫函式時,程式控制將轉移到被呼叫的方法。被呼叫的方法執行已定義的任務,並且當執行其 return 語句或到達其函式結束的閉合大括號時,它將程式控制返回到主程式。

要呼叫方法,您只需將所需的引數與方法名稱一起傳遞,如果方法返回值,則可以儲存返回值。例如:

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
/* method declaration */
- (int)max:(int)num1 andNum2:(int)num2;
@end

@implementation SampleClass

/* method returning the max between two numbers */
- (int)max:(int)num1 andNum2:(int)num2 {

   /* local variable declaration */
   int result;
 
   if (num1 > num2) {
      result = num1;
   } else {
      result = num2;
   }
 
   return result; 
}

@end

int main () {
   
   /* local variable definition */
   int a = 100;
   int b = 200;
   int ret;
   
   SampleClass *sampleClass = [[SampleClass alloc]init];

   /* calling a method to get max value */
   ret = [sampleClass max:a andNum2:b];
 
   NSLog(@"Max value is : %d\n", ret );
   return 0;
}

我將 max() 函式與 main() 函式一起保留並編譯了原始碼。執行最終可執行檔案時,它將產生以下結果:

2013-09-07 22:28:45.912 demo[26080] Max value is : 200

函式引數

如果函式要使用引數,則必須宣告接受引數值的變數。這些變數稱為函式的形式引數

形式引數在函式內部的行為類似於其他區域性變數,並在進入函式時建立,並在退出時銷燬。

呼叫函式時,引數傳遞給函式有兩種方式:

序號 呼叫型別 & 說明
1 按值呼叫

此方法將引數的實際值複製到函式的形式引數中。在這種情況下,對函式內部引數所做的更改不會影響引數。

2 按引用呼叫

此方法將引數的地址複製到形式引數中。在函式內部,該地址用於訪問呼叫中使用的實際引數。這意味著對引數所做的更改會影響引數。

預設情況下,Objective-C 使用按值呼叫傳遞引數。通常,這意味著函式內的程式碼無法更改用於呼叫函式的引數,並且上述示例在呼叫 max() 函式時使用了相同的方法。

Objective-C 塊

Objective-C 類定義一個將資料與相關行為組合的物件。有時,僅表示單個任務或行為單元而不是方法集合是有意義的。

塊是新增到 C、Objective-C 和 C++ 的語言級特性,允許您建立可以像值一樣傳遞給方法或函式的不同程式碼段。塊是 Objective-C 物件,這意味著它們可以新增到像 NSArray 或 NSDictionary 這樣的集合中。它們還可以捕獲來自封閉作用域的值,這使得它們類似於其他程式語言中的閉包或 lambda。

簡單的塊宣告語法

returntype (^blockName)(argumentType);

簡單的塊實現

returntype (^blockName)(argumentType)= ^{
};

這是一個簡單的例子

void (^simpleBlock)(void) = ^{
   NSLog(@"This is a block");
};

我們可以使用以下方法呼叫塊:

simpleBlock();

塊接受引數並返回值

塊也可以像方法和函式一樣接受引數和返回值。

這是一個實現和呼叫帶有引數和返回值的塊的簡單示例。

double (^multiplyTwoValues)(double, double) = 
   ^(double firstValue, double secondValue) {
      return firstValue * secondValue;
   };

double result = multiplyTwoValues(2,4); 
NSLog(@"The result is %f", result);

使用型別定義的塊

這是一個在塊中使用 typedef 的簡單示例。請注意,此示例目前在線上編譯器上不起作用。使用XCode執行相同的程式碼。

#import <Foundation/Foundation.h>

typedef void (^CompletionBlock)();
@interface SampleClass:NSObject
- (void)performActionWithCompletion:(CompletionBlock)completionBlock;
@end

@implementation SampleClass

- (void)performActionWithCompletion:(CompletionBlock)completionBlock {

   NSLog(@"Action Performed");
   completionBlock();
}

@end

int main() {
   
   /* my first program in Objective-C */
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass performActionWithCompletion:^{
      NSLog(@"Completion is called to intimate action is performed.");
   }];

   return 0;
}

讓我們編譯並執行它,它將產生以下結果:

2013-09-10 08:13:57.155 demo[284:303] Action Performed
2013-09-10 08:13:57.157 demo[284:303] Completion is called to intimate action is performed.

塊更多地用於 iOS 應用程式和 Mac OS X。因此,瞭解塊的使用非常重要。

Objective-C 數字

在 Objective-C 程式語言中,為了以物件形式儲存 int、float、bool 等基本資料型別,

Objective-C 提供了一系列用於處理 NSNumber 的方法,重要的方法列在下表中。

序號 方法 & 說明
1

+ (NSNumber *)numberWithBool:(BOOL)value

建立並返回一個包含給定值的 NSNumber 物件,將其視為 BOOL。

2

+ (NSNumber *)numberWithChar:(char)value

建立並返回一個包含給定值的 NSNumber 物件,將其視為帶符號的 char。

3

+ (NSNumber *)numberWithDouble:(double)value

建立一個包含給定值(作為雙精度浮點數處理)的 NSNumber 物件並返回。

4

+ (NSNumber *)numberWithFloat:(float)value

建立一個包含給定值(作為單精度浮點數處理)的 NSNumber 物件並返回。

5

+ (NSNumber *)numberWithInt:(int)value

建立一個包含給定值(作為有符號整型處理)的 NSNumber 物件並返回。

6

+ (NSNumber *)numberWithInteger:(NSInteger)value

建立一個包含給定值(作為 NSInteger 處理)的 NSNumber 物件並返回。

7

- (BOOL)boolValue

將接收者的值作為 BOOL 返回。

8

- (char)charValue

將接收者的值作為字元 (char) 返回。

9

- (double)doubleValue

將接收者的值作為雙精度浮點數 (double) 返回。

10

- (float)floatValue

將接收者的值作為單精度浮點數 (float) 返回。

11

- (NSInteger)integerValue

將接收者的值作為 NSInteger 返回。

12

- (int)intValue

將接收者的值作為整型 (int) 返回。

13

- (NSString *)stringValue

將接收者的值作為人類可讀的字串返回。

這是一個使用 NSNumber 的簡單示例,它將兩個數字相乘並返回乘積。

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (NSNumber *)multiplyA:(NSNumber *)a withB:(NSNumber *)b;
@end

@implementation SampleClass

- (NSNumber *)multiplyA:(NSNumber *)a withB:(NSNumber *)b {
   float number1 = [a floatValue];
   float number2 = [b floatValue];
   float product = number1 * number2;
   NSNumber *result = [NSNumber numberWithFloat:product];
   return result;
}

@end

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

   SampleClass *sampleClass = [[SampleClass alloc]init];
   NSNumber *a = [NSNumber numberWithFloat:10.5];
   NSNumber *b = [NSNumber numberWithFloat:10.0];   
   NSNumber *result = [sampleClass multiplyA:a withB:b];
   NSString *resultString = [result stringValue];
   NSLog(@"The product is %@",resultString);

   [pool drain];
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-14 18:53:40.575 demo[16787] The product is 105

Objective-C 陣列

Objective-C 程式語言提供了一種名為**陣列**的資料結構,它可以儲存大小固定的同類型元素的順序集合。陣列用於儲存資料集合,但通常將陣列視為相同型別的變數集合更有用。

無需宣告單獨的變數,例如 number0、number1……和 number99,您可以宣告一個數組變數,例如 numbers,並使用 numbers[0]、numbers[1]……和 numbers[99] 來表示各個變數。陣列中的特定元素透過索引訪問。

所有陣列都由連續的記憶體位置組成。最低地址對應於第一個元素,最高地址對應於最後一個元素。

Arrays in Objective-C

宣告陣列

要在 Objective-C 中宣告陣列,程式設計師需要指定元素的型別和陣列所需的元素數量,如下所示:

type arrayName [ arraySize ];

這稱為*一維*陣列。**arraySize** 必須是一個大於零的整數常量,而**type** 可以是任何有效的 Objective-C 資料型別。例如,要宣告一個名為**balance** 的包含 10 個元素的雙精度浮點數 (double) 型別陣列,可以使用以下語句:

double balance[10];

現在,*balance* 是一個變數陣列,足以容納最多 10 個雙精度浮點數。

初始化陣列

您可以在 Objective-C 中逐個初始化陣列,也可以使用單個語句進行初始化,如下所示:

double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};

花括號 {} 中的值的數量不能大於方括號 [] 中為陣列宣告的元素數量。以下是如何為陣列賦值單個元素的示例:

如果省略陣列的大小,則會建立一個足夠大的陣列來容納初始化資料。因此,如果您編寫:

double balance[] = {1000.0, 2.0, 3.4, 17.0, 50.0};

您將建立與上一個示例中完全相同的陣列。

balance[4] = 50.0;

上述語句將陣列中第 5 個元素(索引為 4)的值賦值為 50.0。因為所有陣列的第一個元素的索引都是 0(也稱為基索引),所以索引為 4 的元素是第 5 個元素,也就是最後一個元素。以下是上面討論的相同陣列的圖示:

Array Presentation

訪問陣列元素

透過索引陣列名稱來訪問元素。這是透過在陣列名稱後方方括號中放置元素的索引來完成的。例如:

double salary = balance[9];

上述語句將從陣列中取出第 10 個元素並將該值賦值給 salary 變數。以下是一個示例,它將使用所有上述三個概念,即宣告、賦值和訪問陣列:

#import <Foundation/Foundation.h>
 
int main () {
   int n[ 10 ];   /* n is an array of 10 integers */
   int i,j;
 
   /* initialize elements of array n to 0 */         
   for ( i = 0; i < 10; i++ ) {
      n[ i ] = i + 100;    /* set element at location i to i + 100 */
   }
   
   /* output each array element's value */
   for (j = 0; j < 10; j++ ) {
      NSLog(@"Element[%d] = %d\n", j, n[j] );
   }
 
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 01:24:06.669 demo[16508] Element[0] = 100
2013-09-14 01:24:06.669 demo[16508] Element[1] = 101
2013-09-14 01:24:06.669 demo[16508] Element[2] = 102
2013-09-14 01:24:06.669 demo[16508] Element[3] = 103
2013-09-14 01:24:06.669 demo[16508] Element[4] = 104
2013-09-14 01:24:06.669 demo[16508] Element[5] = 105
2013-09-14 01:24:06.669 demo[16508] Element[6] = 106
2013-09-14 01:24:06.669 demo[16508] Element[7] = 107
2013-09-14 01:24:06.669 demo[16508] Element[8] = 108
2013-09-14 01:24:06.669 demo[16508] Element[9] = 109

Objective-C 陣列詳解

陣列對於 Objective-C 非常重要,需要更多細節。Objective-C 程式設計師應該清楚以下幾個與陣列相關的重要的概念:

序號 概念與描述
1 多維陣列

Objective-C 支援多維陣列。多維陣列最簡單的形式是二維陣列。

2 將陣列傳遞給函式

您可以透過指定陣列的名稱(不帶索引)來將指向陣列的指標傳遞給函式。

3 從函式返回陣列

Objective-C 允許函式返回陣列。

4 指向陣列的指標

您可以透過簡單地指定陣列名稱(不帶任何索引)來生成指向陣列第一個元素的指標。

Objective-C 指標

Objective-C 中的指標易於學習且很有趣。一些 Objective-C 程式設計任務使用指標更容易完成,而其他任務(例如動態記憶體分配)則無法在不使用指標的情況下完成。因此,學習指標成為一名完美的 Objective-C 程式設計師是必要的。讓我們以簡單易懂的步驟開始學習它們。

如您所知,每個變數都是一個記憶體位置,每個記憶體位置都有其定義的地址,可以使用取地址符 (&) 運算子訪問,該運算子表示記憶體中的地址。考慮以下示例,它將列印定義的變數的地址:

#import <Foundation/Foundation.h>

int main () {
   int  var1;
   char var2[10];

   NSLog(@"Address of var1 variable: %x\n", &var1 );
   NSLog(@"Address of var2 variable: %x\n", &var2 );

   return 0;
}

編譯並執行上述程式碼時,會產生如下結果:

2013-09-13 03:18:45.727 demo[17552] Address of var1 variable: 1c0843fc
2013-09-13 03:18:45.728 demo[17552] Address of var2 variable: 1c0843f0

因此,您瞭解了什麼是記憶體地址以及如何訪問它,因此概念的基礎已經完成。現在讓我們看看什麼是指標。

什麼是指標?

**指標**是一個變數,其值是另一個變數的地址,即記憶體位置的直接地址。與任何變數或常量一樣,您必須在使用指標儲存任何變數地址之前宣告它。指標變數宣告的一般形式為:

type *var-name;

這裡,**type** 是指標的基型別;它必須是有效的 Objective-C 資料型別,而**var-name** 是指標變數的名稱。用於宣告指標的星號 * 與用於乘法的星號相同。但是,在此語句中,星號用於將變數指定為指標。以下是有效的指標宣告:

int    *ip;    /* pointer to an integer */
double *dp;    /* pointer to a double */
float  *fp;    /* pointer to a float */
char   *ch     /* pointer to a character */

所有指標的實際資料型別,無論是整數、浮點數、字元還是其他型別,都是相同的,都是表示記憶體地址的長十六進位制數。不同資料型別指標之間的唯一區別在於指標指向的變數或常量的型別。

如何使用指標?

有一些重要的操作,我們將經常藉助指標來完成。**(a)**我們定義一個指標變數,**(b)**將變數的地址賦值給指標,以及 **(c)**最終訪問指標變數中可用地址處的值。這是透過使用一元運算子** * **來完成的,該運算子返回位於其運算元指定的地址處的變數的值。以下示例使用了這些操作:

#import <Foundation/Foundation.h>

int main () {
   int  var = 20;    /* actual variable declaration */
   int  *ip;         /* pointer variable declaration */  
   ip = &var;       /* store address of var in pointer variable*/

   NSLog(@"Address of var variable: %x\n", &var  );

   /* address stored in pointer variable */
   NSLog(@"Address stored in ip variable: %x\n", ip );

   /* access the value using the pointer */
   NSLog(@"Value of *ip variable: %d\n", *ip );

   return 0;
}

編譯並執行上述程式碼時,會產生如下結果:

2013-09-13 03:20:21.873 demo[24179] Address of var variable: 337ed41c
2013-09-13 03:20:21.873 demo[24179] Address stored in ip variable: 337ed41c
2013-09-13 03:20:21.874 demo[24179] Value of *ip variable: 20

Objective-C 中的空指標

如果您沒有確切的地址要賦值,則始終建議將 NULL 值賦給指標變數。這是在變數宣告時完成的。賦值為 NULL 的指標稱為**空**指標。

NULL 指標是在多個標準庫中定義的值為零的常量。考慮以下程式:

#import <Foundation/Foundation.h>

int main () {
   int  *ptr = NULL;
   NSLog(@"The value of ptr is : %x\n", ptr  );
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-13 03:21:19.447 demo[28027] The value of ptr is : 0

在大多數作業系統上,程式不允許訪問地址 0 處的記憶體,因為該記憶體由作業系統保留。但是,記憶體地址 0 具有特殊意義;它表示指標並非旨在指向可訪問的記憶體位置。但按照慣例,如果指標包含空(零)值,則假定它不指向任何內容。

要檢查空指標,可以使用 if 語句,如下所示:

if(ptr)     /* succeeds if p is not null */
if(!ptr)    /* succeeds if p is null */

Objective-C 指標詳解

指標有很多但簡單的概念,它們對於 Objective-C 程式設計非常重要。Objective-C 程式設計師應該清楚以下幾個重要的指標概念:

序號 概念與描述
1 Objective-C - 指標算術

有四個算術運算子可用於指標:++、--、+、-

2 Objective-C - 指標陣列

您可以定義陣列以容納多個指標。

3 Objective-C - 指向指標的指標

Objective-C 允許您對指標進行指標操作,依此類推。

4 在 Objective-C 中將指標傳遞給函式

透過引用或透過地址傳遞引數都可以使被呼叫函式在呼叫函式中更改傳遞的引數。

5 在 Objective-C 中從函式返回指標

Objective-C 允許函式返回指向區域性變數、靜態變數和動態分配記憶體的指標。

Objective-C 字串

Objective-C 程式語言中的字串使用 NSString 表示,其子類 NSMutableString 提供了幾種建立字串物件的方法。建立字串物件最簡單的方法是使用 Objective-C @"..." 結構:

NSString *greeting = @"Hello";

下面顯示了一個建立和列印字串的簡單示例。

#import <Foundation/Foundation.h>

int main () {
   NSString *greeting = @"Hello";
   NSLog(@"Greeting message: %@\n", greeting );

   return 0;
}

編譯並執行上述程式碼時,會產生如下結果:

2013-09-11 01:21:39.922 demo[23926] Greeting message: Hello

Objective-C 支援廣泛的字串操作方法:

序號 方法與用途
1

- (NSString *)capitalizedString;

返回接收者的首字母大寫表示形式。

2

- (unichar)characterAtIndex:(NSUInteger)index;

返回給定陣列位置處的字元。

3

- (double)doubleValue;

將接收者文字的浮點值作為雙精度浮點數返回。

4

- (float)floatValue;

將接收者文字的浮點值作為單精度浮點數返回。

5

- (BOOL)hasPrefix:(NSString *)aString;

返回一個布林值,指示給定字串是否與接收者的開頭字元匹配。

6

- (BOOL)hasSuffix:(NSString *)aString;

返回一個布林值,指示給定字串是否與接收者的結尾字元匹配。

7

- (id)initWithFormat:(NSString *)format ...;

返回一個 NSString 物件,該物件使用給定的格式字串作為模板進行初始化,其餘引數值將被替換到該模板中。

8

- (NSInteger)integerValue;

返回接收者文字的 NSInteger 值。

9

- (BOOL)isEqualToString:(NSString *)aString;

返回一個布林值,指示給定字串是否使用基於 Unicode 的逐字比較與接收者相等。

10

- (NSUInteger)length;

返回接收者中的 Unicode 字元數。

11

- (NSString *)lowercaseString;

返回接收者的全小寫表示形式。

12

- (NSRange)rangeOfString:(NSString *)aString;

查詢並返回接收器中給定字串的第一次出現的範圍。

13

- (NSString *)stringByAppendingFormat:(NSString *)format ...;

返回一個字串,該字串是透過將使用給定格式字串和以下引數構造的字串附加到接收者而建立的。

14

- (NSString *)stringByTrimmingCharactersInSet:(NSCharacterSet *)set;

返回一個新的字串,該字串是透過從接收者的兩端刪除給定字元集中包含的字元而建立的。

15

- (NSString *)substringFromIndex:(NSUInteger)anIndex;

返回一個新字串,其中包含從給定索引處的字元到接收器結尾的字元。

以下示例使用了上述一些函式:

#import <Foundation/Foundation.h>

int main () {
   NSString *str1 = @"Hello";
   NSString *str2 = @"World";
   NSString *str3;
   int  len ;

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

   /* uppercase string */
   str3 = [str2 uppercaseString];
   NSLog(@"Uppercase String :  %@\n", str3 );

   /* concatenates str1 and str2 */
   str3 = [str1 stringByAppendingFormat:@"World"];
   NSLog(@"Concatenated string:   %@\n", str3 );

   /* total length of str3 after concatenation */
   len = [str3 length];
   NSLog(@"Length of Str3 :  %d\n", len );

   /* InitWithFormat */
   str3 = [[NSString alloc] initWithFormat:@"%@ %@",str1,str2];	
   NSLog(@"Using initWithFormat:   %@\n", str3 );
   [pool drain];

   return 0;
}

編譯並執行上述程式碼時,會產生如下結果:

2013-09-11 01:15:45.069 demo[30378] Uppercase String :  WORLD
2013-09-11 01:15:45.070 demo[30378] Concatenated string:   HelloWorld
2013-09-11 01:15:45.070 demo[30378] Length of Str3 :  10
2013-09-11 01:15:45.070 demo[30378] Using initWithFormat:   Hello World

您可以在NSString 類參考中找到 Objective-C NSString 相關方法的完整列表。

Objective-C 結構體

Objective-C 陣列允許您定義可以儲存多種相同型別資料項的變數型別,但**結構體**是 Objective-C 程式設計中另一種使用者定義的資料型別,它允許您組合不同型別的資料項。

結構體用於表示記錄。假設您想跟蹤圖書館中書籍的資訊,您可能需要跟蹤每本書的以下屬性:

  • 書名
  • 作者
  • 主題
  • 圖書ID

定義結構體

要定義結構體,必須使用struct語句。struct語句定義了一種新的資料型別,該型別包含多個成員。struct語句的格式如下:

struct [structure tag] {
   member definition;
   member definition;
   ...
   member definition;
} [one or more structure variables];  

結構體標籤是可選的,每個成員定義都是一個普通的變數定義,例如int i; 或float f; 或任何其他有效的變數定義。在結構體定義的末尾,分號之前,您可以指定一個或多個結構體變數,但這也是可選的。以下是宣告Book結構體的方式:

struct Books {
   NSString *title;
   NSString *author;
   NSString *subject;
   int   book_id;
} book;  

訪問結構體成員

要訪問結構體的任何成員,我們使用成員訪問運算子 (.)。成員訪問運算子用作結構體變數名和我們希望訪問的結構體成員之間的句點。您可以使用struct關鍵字定義結構體型別的變數。以下示例解釋了結構體的用法:

#import <Foundation/Foundation.h>

struct Books {
   NSString *title;
   NSString *author;
   NSString *subject;
   int   book_id;
};
 
int main() {
   struct Books Book1;        /* Declare Book1 of type Book */
   struct Books Book2;        /* Declare Book2 of type Book */
 
   /* book 1 specification */
   Book1.title = @"Objective-C Programming";
   Book1.author = @"Nuha Ali"; 
   Book1.subject = @"Objective-C Programming Tutorial";
   Book1.book_id = 6495407;

   /* book 2 specification */
   Book2.title = @"Telecom Billing";
   Book2.author = @"Zara Ali";
   Book2.subject = @"Telecom Billing Tutorial";
   Book2.book_id = 6495700;
 
   /* print Book1 info */
   NSLog(@"Book 1 title : %@\n", Book1.title);
   NSLog(@"Book 1 author : %@\n", Book1.author);
   NSLog(@"Book 1 subject : %@\n", Book1.subject);
   NSLog(@"Book 1 book_id : %d\n", Book1.book_id);

   /* print Book2 info */
   NSLog(@"Book 2 title : %@\n", Book2.title);
   NSLog(@"Book 2 author : %@\n", Book2.author);
   NSLog(@"Book 2 subject : %@\n", Book2.subject);
   NSLog(@"Book 2 book_id : %d\n", Book2.book_id);

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 04:20:07.947 demo[20591] Book 1 title : Objective-C Programming
2013-09-14 04:20:07.947 demo[20591] Book 1 author : Nuha Ali
2013-09-14 04:20:07.947 demo[20591] Book 1 subject : Objective-C Programming Tutorial
2013-09-14 04:20:07.947 demo[20591] Book 1 book_id : 6495407
2013-09-14 04:20:07.947 demo[20591] Book 2 title : Telecom Billing
2013-09-14 04:20:07.947 demo[20591] Book 2 author : Zara Ali
2013-09-14 04:20:07.947 demo[20591] Book 2 subject : Telecom Billing Tutorial
2013-09-14 04:20:07.947 demo[20591] Book 2 book_id : 6495700

結構體作為函式引數

您可以像傳遞其他任何變數或指標一樣傳遞結構體作為函式引數。訪問結構體變數的方式與上述示例中相同:

#import <Foundation/Foundation.h>

struct Books {
   NSString *title;
   NSString *author;
   NSString *subject;
   int   book_id;
};

@interface SampleClass:NSObject
/* function declaration */
- (void) printBook:( struct Books) book ;
@end

@implementation SampleClass 

- (void) printBook:( struct Books) book {
   NSLog(@"Book title : %@\n", book.title);
   NSLog(@"Book author : %@\n", book.author);
   NSLog(@"Book subject : %@\n", book.subject);
   NSLog(@"Book book_id : %d\n", book.book_id);
}

@end

int main() {
   struct Books Book1;        /* Declare Book1 of type Book */
   struct Books Book2;        /* Declare Book2 of type Book */
 
   /* book 1 specification */
   Book1.title = @"Objective-C Programming";
   Book1.author = @"Nuha Ali"; 
   Book1.subject = @"Objective-C Programming Tutorial";
   Book1.book_id = 6495407;

   /* book 2 specification */
   Book2.title = @"Telecom Billing";
   Book2.author = @"Zara Ali";
   Book2.subject = @"Telecom Billing Tutorial";
   Book2.book_id = 6495700;
 
   SampleClass *sampleClass = [[SampleClass alloc]init];
   /* print Book1 info */
   [sampleClass printBook: Book1];

   /* Print Book2 info */
   [sampleClass printBook: Book2];

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 04:34:45.725 demo[8060] Book title : Objective-C Programming
2013-09-14 04:34:45.725 demo[8060] Book author : Nuha Ali
2013-09-14 04:34:45.725 demo[8060] Book subject : Objective-C Programming Tutorial
2013-09-14 04:34:45.725 demo[8060] Book book_id : 6495407
2013-09-14 04:34:45.725 demo[8060] Book title : Telecom Billing
2013-09-14 04:34:45.725 demo[8060] Book author : Zara Ali
2013-09-14 04:34:45.725 demo[8060] Book subject : Telecom Billing Tutorial
2013-09-14 04:34:45.725 demo[8060] Book book_id : 6495700

指向結構體的指標

您可以像定義指向任何其他變數的指標一樣定義指向結構體的指標,如下所示:

struct Books *struct_pointer;

現在,您可以將結構體變數的地址儲存在上面定義的指標變數中。要查詢結構體變數的地址,請在結構體名稱前放置&運算子,如下所示:

struct_pointer = &Book1;

要使用指向該結構體的指標訪問結構體的成員,必須使用->運算子,如下所示:

struct_pointer->title;

讓我們使用結構體指標重寫上面的示例,希望這更容易理解這個概念:

#import <Foundation/Foundation.h>

struct Books {
   NSString *title;
   NSString *author;
   NSString *subject;
   int   book_id;
};

@interface SampleClass:NSObject
/* function declaration */
- (void) printBook:( struct Books *) book ;
@end

@implementation SampleClass 
- (void) printBook:( struct Books *) book {
   NSLog(@"Book title : %@\n", book->title);
   NSLog(@"Book author : %@\n", book->author);
   NSLog(@"Book subject : %@\n", book->subject);
   NSLog(@"Book book_id : %d\n", book->book_id);
}

@end

int main() {
   struct Books Book1;        /* Declare Book1 of type Book */
   struct Books Book2;        /* Declare Book2 of type Book */
 
   /* book 1 specification */
   Book1.title = @"Objective-C Programming";
   Book1.author = @"Nuha Ali"; 
   Book1.subject = @"Objective-C Programming Tutorial";
   Book1.book_id = 6495407;

   /* book 2 specification */
   Book2.title = @"Telecom Billing";
   Book2.author = @"Zara Ali";
   Book2.subject = @"Telecom Billing Tutorial";
   Book2.book_id = 6495700;
 
   SampleClass *sampleClass = [[SampleClass alloc]init];
   /* print Book1 info by passing address of Book1 */
   [sampleClass printBook:&Book1];

   /* print Book2 info by passing address of Book2 */
   [sampleClass printBook:&Book2];

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 04:38:13.942 demo[20745] Book title : Objective-C Programming
2013-09-14 04:38:13.942 demo[20745] Book author : Nuha Ali
2013-09-14 04:38:13.942 demo[20745] Book subject : Objective-C Programming Tutorial
2013-09-14 04:38:13.942 demo[20745] Book book_id : 6495407
2013-09-14 04:38:13.942 demo[20745] Book title : Telecom Billing
2013-09-14 04:38:13.942 demo[20745] Book author : Zara Ali
2013-09-14 04:38:13.942 demo[20745] Book subject : Telecom Billing Tutorial
2013-09-14 04:38:13.942 demo[20745] Book book_id : 6495700

位域

位域允許在結構體中打包資料。當記憶體或資料儲存非常寶貴時,這尤其有用。典型的例子:

  • 將多個物件打包到一個機器字中。例如,可以壓縮1位標誌。

  • 讀取外部檔案格式——可以讀取非標準檔案格式。例如,9位整數。

Objective-C允許我們在結構體定義中透過在變數後新增:bit length來實現這一點。例如:

struct packed_struct {
   unsigned int f1:1;
   unsigned int f2:1;
   unsigned int f3:1;
   unsigned int f4:1;
   unsigned int type:4;
   unsigned int my_int:9;
} pack;

這裡,packed_struct包含6個成員:四個1位標誌f1..f3,一個4位型別和一個9位my_int。

Objective-C會自動儘可能緊湊地打包上述位域,前提是欄位的最大長度小於或等於計算機的整數字長。如果不是這種情況,則某些編譯器可能允許欄位的記憶體重疊,而其他編譯器則將下一個欄位儲存在下一個字中。

Objective-C預處理器

Objective-C預處理器不是編譯器的一部分,而是編譯過程中的一個單獨步驟。簡單來說,Objective-C預處理器只是一個文字替換工具,它指示編譯器在實際編譯之前進行必要的預處理。我們將Objective-C預處理器稱為OCPP。

所有預處理器命令都以井號 (#) 開頭。它必須是第一個非空字元,為了可讀性,預處理器指令應從第一列開始。以下部分列出了所有重要的預處理器指令:

序號 指令 & 說明
1

#define

替換預處理器宏

2

#include

插入來自另一個檔案的特定標頭檔案

3

#undef

取消定義預處理器宏

4

#ifdef

如果此宏已定義,則返回true

5

#ifndef

如果此宏未定義,則返回true

6

#if

測試編譯時條件是否為true

7

#else

#if的替代方案

8

#elif

#else和#if合併在一條語句中

9

#endif

結束預處理器條件

10

#error

在stderr上列印錯誤訊息

11

#pragma

使用標準化方法向編譯器發出特殊命令

預處理器示例

分析以下示例以瞭解各種指令。

#define MAX_ARRAY_LENGTH 20

此指令告訴OCPP將MAX_ARRAY_LENGTH的所有例項替換為20。使用#define定義常量以提高可讀性。

#import <Foundation/Foundation.h>
#include "myheader.h"

這些指令告訴OCPP從Foundation Framework獲取foundation.h並將文字新增到當前原始檔中。下一行告訴OCPP從本地目錄獲取myheader.h並將內容新增到當前原始檔中。

#undef  FILE_SIZE
#define FILE_SIZE 42

這告訴OCPP取消定義現有的FILE_SIZE並將其定義為42。

#ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif

這告訴OCPP僅在MESSAGE尚未定義的情況下定義MESSAGE。

#ifdef DEBUG
   /* Your debugging statements here */
#endif

這告訴OCPP如果定義了DEBUG,則處理包含的語句。如果您在編譯時向gcc編譯器傳遞-DDEBUG標誌,這將很有用。這將定義DEBUG,因此您可以在編譯期間動態地開啟和關閉除錯。

預定義宏

ANSI C定義了許多宏。儘管每個宏都可用於您的程式設計,但不應直接修改預定義宏。

序號 宏 & 說明
1

__DATE__

當前日期,以“MMM DD YYYY”格式的字元文字表示

2

__TIME__

當前時間,以“HH:MM:SS”格式的字元文字表示

3

__FILE__

包含當前檔名,作為字串文字。

4

__LINE__

包含當前行號,作為十進位制常量。

5

__STDC__

當編譯器符合ANSI標準時定義為1。

讓我們嘗試以下示例:

#import <Foundation/Foundation.h>

int main() {
   NSLog(@"File :%s\n", __FILE__ );
   NSLog(@"Date :%s\n", __DATE__ );
   NSLog(@"Time :%s\n", __TIME__ );
   NSLog(@"Line :%d\n", __LINE__ );
   NSLog(@"ANSI :%d\n", __STDC__ );
   
   return 0;
}

當以上程式碼在一個名為main.m的檔案中編譯並執行時,它會產生以下結果:

2013-09-14 04:46:14.859 demo[20683] File :main.m
2013-09-14 04:46:14.859 demo[20683] Date :Sep 14 2013
2013-09-14 04:46:14.859 demo[20683] Time :04:46:14
2013-09-14 04:46:14.859 demo[20683] Line :8
2013-09-14 04:46:14.859 demo[20683] ANSI :1

預處理器運算子

Objective-C預處理器提供以下運算子來幫助您建立宏:

宏延續符 (\)

宏通常必須包含在一行中。宏延續符用於延續過長而無法放在單行中的宏。例如:

#define  message_for(a, b)  \
   NSLog(@#a " and " #b ": We love you!\n")

字串化 (#)

字串化或井號運算子('#'),當在宏定義中使用時,會將宏引數轉換為字串常量。此運算子只能用於具有指定引數列表的宏。例如:

#import <Foundation/Foundation.h>

#define  message_for(a, b)  \
   NSLog(@#a " and " #b ": We love you!\n")

int main(void) {
   message_for(Carole, Debra);
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 05:46:14.859 demo[20683] Carole and Debra: We love you!

令牌貼上 (##)

宏定義中的令牌貼上運算子 (##) 將兩個引數組合在一起。它允許宏定義中的兩個單獨令牌連線成一個令牌。例如:

#import <Foundation/Foundation.h>

#define tokenpaster(n) NSLog (@"token" #n " = %d", token##n)

int main(void) {
   int token34 = 40;
   
   tokenpaster(34);
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 05:48:14.859 demo[20683] token34 = 40

它是怎麼發生的,因為這個例子導致預處理器產生以下實際輸出:

NSLog (@"token34 = %d", token34);

此示例顯示了token##n與token34的連線,這裡我們同時使用了字串化令牌貼上

defined() 運算子

預處理器defined運算子用於常量表達式中,以確定是否使用#define定義了識別符號。如果指定了識別符號,則值為true(非零)。如果未定義符號,則值為false(零)。defined運算子的指定方式如下:

#import <Foundation/Foundation.h>

#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

int main(void) {
   NSLog(@"Here is the message: %s\n", MESSAGE);  
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 05:48:19.859 demo[20683] Here is the message: You wish!

引數化宏

OCPP 的強大功能之一是能夠使用引數化宏來模擬函式。例如,我們可能有一些程式碼來計算一個數字的平方,如下所示:

int square(int x) {
   return x * x;
}

我們可以使用宏重寫上面的程式碼,如下所示:

#define square(x) ((x) * (x))

帶引數的宏必須在使用之前使用#define指令定義。引數列表括在括號中,並且必須緊跟在宏名稱之後。宏名稱和開括號之間不允許有空格。例如:

#import <Foundation/Foundation.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(void) {
   NSLog(@"Max between 20 and 10 is %d\n", MAX(10, 20));  
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 05:52:15.859 demo[20683] Max between 20 and 10 is 20

Objective-C typedef

Objective-C程式語言提供了一個名為typedef的關鍵字,您可以使用它為型別賦予新名稱。以下是一個為單位元組數字定義術語BYTE的示例:

typedef unsigned char BYTE;

在此型別定義之後,識別符號BYTE可以用作型別unsigned char的縮寫,例如:

BYTE  b1, b2;

按照約定,大寫字母用於這些定義,以提醒使用者型別名稱實際上是一個符號縮寫,但是您可以使用小寫字母,如下所示:

typedef unsigned char byte;

您也可以使用typedef為使用者定義的資料型別命名。例如,您可以將typedef與結構體一起使用來定義新的資料型別,然後直接使用該資料型別定義結構體變數,如下所示:

#import <Foundation/Foundation.h>

typedef struct Books {
   NSString *title;
   NSString *author;
   NSString *subject;
   int book_id;
} Book;
 
int main() {
   Book book;
   book.title = @"Objective-C Programming";
   book.author = @"TutorialsPoint";
   book.subject = @"Programming tutorial";
   book.book_id = 100;
   
   NSLog( @"Book title : %@\n", book.title);
   NSLog( @"Book author : %@\n", book.author);
   NSLog( @"Book subject : %@\n", book.subject);
   NSLog( @"Book Id : %d\n", book.book_id);

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-12 12:21:53.745 demo[31183] Book title : Objective-C Programming
2013-09-12 12:21:53.745 demo[31183] Book author : TutorialsPoint
2013-09-12 12:21:53.745 demo[31183] Book subject : Programming tutorial
2013-09-12 12:21:53.745 demo[31183] Book Id : 100

typedef vs #define

#define是一個Objective-C指令,它也用於定義各種資料型別的別名,類似於typedef,但存在以下區別:

  • typedef僅限於為型別賦予符號名稱,而#define也可用於為值定義別名,例如,您可以將1定義為ONE等。

  • typedef的解釋由編譯器執行,而#define語句由預處理器處理。

以下是#define最簡單的用法:

#import <Foundation/Foundation.h>
 
#define TRUE  1
#define FALSE 0
 
int main( ) {
   NSLog( @"Value of TRUE : %d\n", TRUE);
   NSLog( @"Value of FALSE : %d\n", FALSE);

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-12 12:23:37.993 demo[5160] Value of TRUE : 1
2013-09-12 12:23:37.994 demo[5160] Value of FALSE : 0

Objective-C型別轉換

型別轉換是一種將變數從一種資料型別轉換為另一種資料型別的方法。例如,如果要將長整型值儲存到簡單整型中,則可以將長整型轉換為整型。您可以使用強制型別轉換運算子顯式地將值從一種型別轉換為另一種型別,如下所示:

(type_name) expression

在Objective-C中,我們通常使用CGFloat進行浮點運算,在32位情況下它派生自float基本型別,在64位情況下派生自double型別。考慮以下示例,其中強制型別轉換運算子導致一個整型變數除以另一個整型變數的操作作為浮點運算執行:

#import <Foundation/Foundation.h>

int main() {
   int sum = 17, count = 5;
   CGFloat mean;

   mean = (CGFloat) sum / count;
   NSLog(@"Value of mean : %f\n", mean );

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-11 01:35:40.047 demo[20634] Value of mean : 3.400000

這裡需要注意的是,強制型別轉換運算子的優先順序高於除法,因此sum的值首先轉換為double型別,最後它除以count,得到一個double值。

型別轉換可以是隱式的,由編譯器自動執行;也可以透過使用型別轉換運算子顯式指定。在需要型別轉換時使用型別轉換運算子被認為是良好的程式設計實踐。

整數提升

整數提升是一個過程,它將小於intunsigned int型別的整數值轉換為intunsigned int。考慮一個將字元加到整數中的例子:

#import <Foundation/Foundation.h>

int main() {
   int  i = 17;
   char c = 'c';  /* ascii value is 99 */
   int sum;

   sum = i + c;
   NSLog(@"Value of sum : %d\n", sum );

   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-11 01:38:28.492 demo[980] Value of sum : 116

這裡,sum的值為116,因為編譯器正在進行整數提升,並在執行實際加法運算之前將'c'的值轉換為ASCII碼。

通常的算術轉換

通常的算術轉換會隱式地執行,以將它們的值轉換為公共型別。編譯器首先執行整數提升,如果運算元的型別仍然不同,則將其轉換為以下層次結構中出現最高的型別:

Usual Arithmetic Conversion

通常的算術轉換不適用於賦值運算子,也不適用於邏輯運算子 && 和 ||。讓我們來看下面的例子來理解這個概念:

#import <Foundation/Foundation.h>

int main() {
   int  i = 17;
   char c = 'c';  /* ascii value is 99 */
   CGFloat sum;

   sum = i + c;
   NSLog(@"Value of sum : %f\n", sum );
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-11 01:41:39.192 demo[15351] Value of sum : 116.000000

這裡很容易理解,c首先被轉換為整數,但由於最終值是浮點數,所以應用了通常的算術轉換,編譯器將i和c轉換為浮點數並相加,得到一個浮點數結果。

Objective-C日誌處理

NSLog方法

為了列印日誌,我們在Objective-C程式語言中使用NSLog方法,從HelloWorld示例中我們就用過它了。

讓我們來看一個簡單的程式碼,它將列印“Hello World”字樣:

#import <Foundation/Foundation.h>

int main() {
   NSLog(@"Hello, World! \n");
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-16 00:32:50.888 demo[16669] Hello, World! 

在正式應用中停用日誌

由於我們在應用程式中使用的NSLog會在裝置日誌中打印出來,所以在正式版本中列印日誌是不好的。因此,我們使用一種型別定義來列印日誌,我們可以像下面這樣使用它們。

#import <Foundation/Foundation.h>

#if DEBUG == 0
#define DebugLog(...)
#elif DEBUG == 1
#define DebugLog(...) NSLog(__VA_ARGS__)
#endif

int main() {
   DebugLog(@"Debug log, our custom addition gets \
   printed during debug only" );
   NSLog(@"NSLog gets printed always" );     
   return 0;
}

現在,當我們在除錯模式下編譯並執行程式時,我們將得到以下結果。

2013-09-11 02:47:07.723 demo[618] Debug log, our custom addition gets printed during debug only
2013-09-11 02:47:07.723 demo[618] NSLog gets printed always

現在,當我們在釋出模式下編譯並執行程式時,我們將得到以下結果。

2013-09-11 02:47:45.248 demo[3158] NSLog gets printed always

Objective-C錯誤處理

在Objective-C程式設計中,錯誤處理由Foundation框架中提供的NSError類提供。

NSError物件封裝了比僅使用錯誤程式碼或錯誤字串更豐富、更可擴充套件的錯誤資訊。NSError物件的核心屬性是錯誤域(用字串表示)、特定於域的錯誤程式碼和包含應用程式特定資訊的使用者資訊字典。

NSError

Objective-C程式使用NSError物件來傳達使用者需要了解的執行時錯誤資訊。在大多數情況下,程式會在對話方塊或表單中顯示此錯誤資訊。但它也可能解釋這些資訊,並要求使用者嘗試從錯誤中恢復,或自行嘗試糾正錯誤。

NSError物件包含:

  • - 錯誤域可以是預定義的NSError域之一,也可以是描述自定義域的任意字串,並且域不能為nil。

  • 程式碼 - 錯誤的錯誤程式碼。

  • 使用者資訊 - 錯誤的userInfo字典,userInfo可以為nil。

以下示例顯示如何建立自定義錯誤

NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
NSString *desc = NSLocalizedString(@"Unable to complete the process", @"");
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
NSError *error = [NSError errorWithDomain:domain code:-101 userInfo:userInfo];

以下是上面作為指標引用傳遞的錯誤示例的完整程式碼:

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
-(NSString *) getEmployeeNameForID:(int) id withError:(NSError **)errorPtr;
@end

@implementation SampleClass

-(NSString *) getEmployeeNameForID:(int) id withError:(NSError **)errorPtr {
   if(id == 1) {
      return @"Employee Test Name";
   } else {
      NSString *domain = @"com.MyCompany.MyApplication.ErrorDomain";
      NSString *desc =@"Unable to complete the process";
      NSDictionary *userInfo = [[NSDictionary alloc] 
      initWithObjectsAndKeys:desc,
      @"NSLocalizedDescriptionKey",NULL];  
      *errorPtr = [NSError errorWithDomain:domain code:-101 
      userInfo:userInfo];
      return @"";
   }
}

@end

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   SampleClass *sampleClass = [[SampleClass alloc]init];
   NSError *error = nil;
   NSString *name1 = [sampleClass getEmployeeNameForID:1 withError:&error];
  
   if(error) {
      NSLog(@"Error finding Name1: %@",error);
   } else {
      NSLog(@"Name1: %@",name1);
   }
   
   error = nil;
   NSString *name2 = [sampleClass getEmployeeNameForID:2 withError:&error];

   if(error) {
      NSLog(@"Error finding Name2: %@",error);
   } else {
      NSLog(@"Name2: %@",name2);
   }

   [pool drain];
   return 0; 
}

在上面的例子中,如果id為1,我們返回一個名稱,否則我們設定使用者定義的錯誤物件。

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-14 18:01:00.809 demo[27632] Name1: Employee Test Name
2013-09-14 18:01:00.809 demo[27632] Error finding Name2: Unable to complete the process

命令列引數

當執行Objective-C程式時,可以從命令列傳遞一些值給它。這些值稱為命令列引數,它們在很多時候對你的程式很重要,尤其是在你想從外部控制程式而不是在程式碼中硬編碼這些值的時候。

命令列引數使用main()函式引數處理,其中argc指的是傳遞的引數數量,argv[]是一個指標陣列,指向傳遞給程式的每個引數。下面是一個簡單的例子,它檢查命令列是否提供了任何引數,並據此採取行動:

#import <Foundation/Foundation.h>

int main( int argc, char *argv[] ) {
   if( argc == 2 ) {
      NSLog(@"The argument supplied is %s\n", argv[1]);
   } else if( argc > 2 ) {
      NSLog(@"Too many arguments supplied.\n");
   } else {
      NSLog(@"One argument expected.\n");
   }
}

當以上程式碼編譯並使用單個引數(例如“testing”)執行時,會產生以下結果。

2013-09-13 03:01:17.333 demo[7640] The argument supplied is testing

當以上程式碼編譯並使用兩個引數(例如testing1和testing2)執行時,會產生以下結果。

2013-09-13 03:01:18.333 demo[7640] Too many arguments supplied.

當以上程式碼編譯並執行時不傳遞任何引數,會產生以下結果。

2013-09-13 03:01:18.333 demo[7640] One argument expected

需要注意的是,argv[0]儲存程式本身的名稱,argv[1]是指向提供的第一個命令列引數的指標,*argv[n]是最後一個引數。如果沒有提供引數,argc將為1,否則如果傳遞一個引數,則argc設定為2。

你用空格分隔所有命令列引數,但是如果引數本身包含空格,那麼你可以透過將它們放在雙引號""或單引號''內來傳遞這些引數。讓我們再次重寫上面的例子,我們將列印程式名稱,並且我們還透過將命令列引數放在雙引號內來傳遞它:

#import <Foundation/Foundation.h>

int main( int argc, char *argv[] ) {
   NSLog(@"Program name %s\n", argv[0]);
 
   if( argc == 2 ) {
      NSLog(@"The argument supplied is %s\n", argv[1]);
   } else if( argc > 2 ) {
      NSLog(@"Too many arguments supplied.\n");
   } else {
      NSLog(@"One argument expected.\n");
   }
   
   return 0;
}

當以上程式碼編譯並使用一個用空格分隔但在雙引號內(例如“Testing1 Testing2”)的單個引數執行時,會產生以下結果。

2017-11-30 06:36:59.081 main[71010] Program name main
2017-11-30 06:36:59.082 main[71010] One argument expected.

Objective-C類與物件

Objective-C程式語言的主要目的是為C程式語言新增面向物件特性,類是Objective-C支援面向物件程式設計的核心特性,通常被稱為使用者定義型別。

類用於指定物件的結構,它將資料表示和操作該資料的方法組合成一個簡潔的包。類中的資料和方法稱為類的成員。

Objective-C特性

  • 類定義在兩個不同的部分,即@interface@implementation

  • 幾乎所有東西都是物件的形式。

  • 物件接收訊息,物件通常被稱為接收者。

  • 物件包含例項變數。

  • 物件和例項變數具有作用域。

  • 類隱藏物件的實現。

  • 屬性用於在其他類中訪問類例項變數。

Objective-C類定義

定義類時,定義的是資料型別的藍圖。這實際上並沒有定義任何資料,但它確實定義了類名的含義,即類的物件將包含什麼以及可以對該物件執行什麼操作。

類定義以關鍵字@interface開頭,後跟介面(類)名稱;以及用一對花括號括起來的類體。在Objective-C中,所有類都派生自名為NSObject的基類。它是所有Objective-C類的超類。它提供諸如記憶體分配和初始化之類的基本方法。例如,我們使用關鍵字class定義Box資料型別如下:

@interface Box:NSObject {
   //Instance variables
   double length;    // Length of a box
   double breadth;   // Breadth of a box
}
@property(nonatomic, readwrite) double height;  // Property

@end

例項變數是私有的,只能在類實現內部訪問。

分配和初始化Objective-C物件

類提供了物件的藍圖,因此基本上物件是由類建立的。我們使用與宣告基本型別變數完全相同的宣告來宣告類的物件。以下語句聲明瞭Box類的兩個物件:

Box box1 = [[Box alloc]init];     // Create box1 object of type Box
Box box2 = [[Box alloc]init];     // Create box2 object of type Box

box1和box2這兩個物件都將擁有自己的一份資料成員副本。

訪問資料成員

可以使用直接成員訪問運算子(.)訪問類物件的屬性。讓我們嘗試以下示例來說明:

#import <Foundation/Foundation.h>

@interface Box:NSObject {
   double length;    // Length of a box
   double breadth;   // Breadth of a box
   double height;    // Height of a box
}

@property(nonatomic, readwrite) double height;  // Property
-(double) volume;
@end

@implementation Box

@synthesize height; 

-(id)init {
   self = [super init];
   length = 1.0;
   breadth = 1.0;
   return self;
}

-(double) volume {
   return length*breadth*height;
}

@end

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];    
   Box *box1 = [[Box alloc]init];    // Create box1 object of type Box
   Box *box2 = [[Box alloc]init];    // Create box2 object of type Box

   double volume = 0.0;             // Store the volume of a box here
 
   // box 1 specification
   box1.height = 5.0; 

   // box 2 specification
   box2.height = 10.0;
  
   // volume of box 1
   volume = [box1 volume];
   NSLog(@"Volume of Box1 : %f", volume);
   
   // volume of box 2
   volume = [box2 volume];
   NSLog(@"Volume of Box2 : %f", volume);
   
   [pool drain];
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-22 21:25:33.314 ClassAndObjects[387:303] Volume of Box1 : 5.000000
2013-09-22 21:25:33.316 ClassAndObjects[387:303] Volume of Box2 : 10.000000

屬性

在Objective-C中引入屬性是為了確保可以從類外部訪問類的例項變數。

屬性的各個部分如下。
  • 屬性以關鍵字@property開頭

  • 後跟訪問說明符,即nonatomic或atomic,readwrite或readonly以及strong,unsafe_unretained或weak。這取決於變數的型別。對於任何指標型別,我們可以使用strong,unsafe_unretained或weak。類似地,對於其他型別,我們可以使用readwrite或readonly。

  • 後面是變數的資料型別。

  • 最後,我們有一個以分號結尾的屬性名稱。

  • 我們可以在實現類中新增synthesize語句。但在最新的XCode中,合成部分由XCode處理,不需要包含synthesize語句。

只有使用屬性,我們才能訪問類的例項變數。實際上,內部為屬性建立了getter和setter方法。

例如,假設我們有一個屬性@property (nonatomic ,readonly ) BOOL isDone。在幕後,建立瞭如下所示的setter和getter:

-(void)setIsDone(BOOL)isDone;
-(BOOL)isDone;

Objective-C繼承

面向物件程式設計中最重要的概念之一是繼承。繼承允許我們根據另一個類來定義類,這使得建立和維護應用程式更容易。這也提供了重用程式碼功能和加快實現時間的機會。

建立類時,程式設計師無需編寫全新的資料成員和成員函式,而是可以指定新類應該繼承現有類的成員。這個現有類稱為基類,新類稱為派生類

繼承的思想實現了is a關係。例如,哺乳動物IS-A動物,狗IS-A哺乳動物,因此狗IS-A動物等等。

基類和派生類

Objective-C只允許多層繼承,即它只能有一個基類,但允許多層繼承。Objective-C中的所有類都派生自超類NSObject

@interface derived-class: base-class

考慮一個基類Person及其派生類Employee如下:

#import <Foundation/Foundation.h>
 
@interface Person : NSObject {
   NSString *personName;
   NSInteger personAge;
}

- (id)initWithName:(NSString *)name andAge:(NSInteger)age;
- (void)print;

@end

@implementation Person

- (id)initWithName:(NSString *)name andAge:(NSInteger)age {
   personName = name;
   personAge = age;
   return self;
}

- (void)print {
   NSLog(@"Name: %@", personName);
   NSLog(@"Age: %ld", personAge);
}

@end

@interface Employee : Person {
   NSString *employeeEducation;
}

- (id)initWithName:(NSString *)name andAge:(NSInteger)age 
  andEducation:(NSString *)education;
- (void)print;
@end

@implementation Employee

- (id)initWithName:(NSString *)name andAge:(NSInteger)age 
   andEducation: (NSString *)education {
      personName = name;
      personAge = age;
      employeeEducation = education;
      return self;
   }

- (void)print {
   NSLog(@"Name: %@", personName);
   NSLog(@"Age: %ld", personAge);
   NSLog(@"Education: %@", employeeEducation);
}

@end

int main(int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];        
   NSLog(@"Base class Person Object");
   Person *person = [[Person alloc]initWithName:@"Raj" andAge:5];
   [person print];
   NSLog(@"Inherited Class Employee Object");
   Employee *employee = [[Employee alloc]initWithName:@"Raj" 
   andAge:5 andEducation:@"MBA"];
   [employee print];        
   [pool drain];
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-22 21:20:09.842 Inheritance[349:303] Base class Person Object
2013-09-22 21:20:09.844 Inheritance[349:303] Name: Raj
2013-09-22 21:20:09.844 Inheritance[349:303] Age: 5
2013-09-22 21:20:09.845 Inheritance[349:303] Inherited Class Employee Object
2013-09-22 21:20:09.845 Inheritance[349:303] Name: Raj
2013-09-22 21:20:09.846 Inheritance[349:303] Age: 5
2013-09-22 21:20:09.846 Inheritance[349:303] Education: MBA

訪問控制和繼承

如果派生類在介面類中定義,則它可以訪問其基類的所有私有成員,但它不能訪問在實現檔案中定義的私有成員。

我們可以按照誰可以訪問它們來總結不同的訪問型別:

派生類繼承所有基類方法和變數,但以下情況除外:

  • 使用擴充套件在實現檔案中宣告的變數不可訪問。

  • 在實現檔案中使用擴充套件宣告的方法是不可訪問的。

  • 如果繼承類實現了基類中的方法,則執行派生類中的方法。

Objective-C 多型性

多型性這個詞意味著具有多種形式。通常,當存在類層次結構且它們透過繼承相關聯時,就會發生多型性。

Objective-C 多型性意味著對成員函式的呼叫將導致執行不同的函式,具體取決於呼叫該函式的物件型別。

考慮這個例子,我們有一個 Shape 類,它為所有形狀提供基本的介面。Square 和 Rectangle 派生自基類 Shape。

我們有一個 printArea 方法,它將展示面向物件特性多型性

#import <Foundation/Foundation.h>

@interface Shape : NSObject {
   CGFloat area;
}

- (void)printArea;
- (void)calculateArea;
@end

@implementation Shape
- (void)printArea {
   NSLog(@"The area is %f", area);
}

- (void)calculateArea {

}

@end

@interface Square : Shape {
   CGFloat length;
}

- (id)initWithSide:(CGFloat)side;
- (void)calculateArea;

@end

@implementation Square
- (id)initWithSide:(CGFloat)side {
   length = side;
   return self;
}

- (void)calculateArea {
   area = length * length;
}

- (void)printArea {
   NSLog(@"The area of square is %f", area);
}

@end

@interface Rectangle : Shape {
   CGFloat length;
   CGFloat breadth;
}

- (id)initWithLength:(CGFloat)rLength andBreadth:(CGFloat)rBreadth;
@end

@implementation Rectangle
- (id)initWithLength:(CGFloat)rLength andBreadth:(CGFloat)rBreadth {
   length = rLength;
   breadth = rBreadth;
   return self;
}

- (void)calculateArea {
   area = length * breadth;
}

@end

int main(int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   Shape *square = [[Square alloc]initWithSide:10.0];
   [square calculateArea];
   [square printArea];
   Shape *rect = [[Rectangle alloc]
   initWithLength:10.0 andBreadth:5.0];
   [rect calculateArea];
   [rect printArea];        
   [pool drain];
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-22 21:21:50.785 Polymorphism[358:303] The area of square is 100.000000
2013-09-22 21:21:50.786 Polymorphism[358:303] The area is 50.000000

在上面的例子中,根據 calculateArea 和 printArea 方法的可用性,基類或派生類中的方法將被執行。

多型性根據兩個類的實現方法來處理基類和派生類之間方法的切換。

Objective-C 資料封裝

所有 Objective-C 程式都由以下兩個基本元素組成:

  • 程式語句(程式碼) - 這是程式執行操作的部分,它們被稱為方法。

  • 程式資料 - 資料是程式的資訊,受程式函式的影響。

封裝是一個面向物件程式設計的概念,它將資料和操作資料的函式繫結在一起,並保護兩者免受外部干擾和誤用。資料封裝導致了重要的面向物件程式設計概念資料隱藏

資料封裝是將資料及其使用它們的函式捆綁在一起的機制,而資料抽象是隻公開介面並向用戶隱藏實現細節的機制。

Objective-C 透過建立稱為的使用者定義型別來支援封裝和資料隱藏的特性。例如:

@interface Adder : NSObject {
   NSInteger total;
}

- (id)initWithInitialNumber:(NSInteger)initialNumber;
- (void)addNumber:(NSInteger)newNumber;
- (NSInteger)getTotal;

@end

變數 total 是私有的,我們無法從類外部訪問它。這意味著它們只能被 Adder 類的其他成員訪問,而不能被程式的任何其他部分訪問。這就是實現封裝的一種方式。

介面檔案中的方法是可訪問的,並且作用域是公共的。

有一些私有方法,它們是在擴充套件的幫助下編寫的,我們將在接下來的章節中學習。

資料封裝示例

任何實現包含公共和私有成員變數的類的 Objective-C 程式都是資料封裝和資料抽象的示例。考慮以下示例:

#import <Foundation/Foundation.h>

@interface Adder : NSObject {
   NSInteger total;
}

- (id)initWithInitialNumber:(NSInteger)initialNumber;
- (void)addNumber:(NSInteger)newNumber;
- (NSInteger)getTotal;

@end

@implementation Adder
-(id)initWithInitialNumber:(NSInteger)initialNumber {
   total = initialNumber;
   return self;
}

- (void)addNumber:(NSInteger)newNumber {
   total = total + newNumber;
}

- (NSInteger)getTotal {
   return total;
}

@end

int main(int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];        
   Adder *adder = [[Adder alloc]initWithInitialNumber:10];
   [adder addNumber:5];
   [adder addNumber:4];
   
   NSLog(@"The total is %ld",[adder getTotal]);
   [pool drain];
   return 0;
}

編譯並執行上述程式碼時,它會產生以下結果:

2013-09-22 21:17:30.485 DataEncapsulation[317:303] The total is 19

上面的類將數字加在一起並返回總和。公共成員addNumgetTotal 是面向外部世界的介面,使用者需要知道它們才能使用該類。私有成員total 是隱藏在外部世界的東西,但對於類的正常執行是必要的。

設計策略

我們大多數人都透過痛苦的經驗教訓學習到,除非我們真的需要公開類成員,否則預設情況下應該將它們設為私有。這僅僅是良好的封裝

理解資料封裝非常重要,因為它是包括 Objective-C 在內的所有面向物件程式設計 (OOP) 語言的核心特性之一。

Objective-C 分類

有時,您可能會發現希望透過新增僅在某些情況下有用的行為來擴充套件現有類。為了向現有類新增此類擴充套件,Objective-C 提供了分類擴充套件

如果您需要向現有類新增方法,也許是為了新增功能以方便在您自己的應用程式中執行某些操作,最簡單的方法是使用分類。

宣告分類的語法使用 @interface 關鍵字,就像標準的 Objective-C 類描述一樣,但不表示從子類繼承。相反,它在括號中指定分類的名稱,如下所示:

@interface ClassName (CategoryName)

@end

分類的特性

  • 可以為任何類宣告分類,即使您沒有原始實現原始碼。

  • 在分類中宣告的任何方法都可用於原始類的所有例項以及原始類的任何子類。

  • 在執行時,由分類新增的方法與原始類實現的方法之間沒有區別。

現在,讓我們來看一個分類實現示例。讓我們向 Cocoa 類 NSString 新增一個分類。此分類將使我們能夠新增一個新的 getCopyRightString 方法,該方法可以幫助我們返回版權字串。如下所示。

#import <Foundation/Foundation.h>

@interface NSString(MyAdditions)
+(NSString *)getCopyRightString;
@end

@implementation NSString(MyAdditions)

+(NSString *)getCopyRightString {
   return @"Copyright TutorialsPoint.com 2013";
}

@end

int main(int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   NSString *copyrightString = [NSString getCopyRightString];
   NSLog(@"Accessing Category: %@",copyrightString);
   
   [pool drain];
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-22 21:19:12.125 Categories[340:303] Accessing Category: Copyright TutorialsPoint.com 2013

即使由分類新增的任何方法都可用於該類及其子類的所有例項,您也需要在希望使用附加方法的任何原始碼檔案中匯入分類標頭檔案,否則您將遇到編譯器警告和錯誤。

在我們的示例中,由於我們只有一個類,因此我們沒有包含任何標頭檔案,在這種情況下,我們應該像上面所說的那樣包含標頭檔案。

Objective-C 偽裝

在開始講解 Objective-C 中的偽裝之前,我想提請您注意,偽裝在 Mac OS X 10.5 中已被棄用,此後不再可用。因此,對於那些不關心這些已棄用方法的人來說,可以跳過本章。

Objective-C 允許一個類完全替換程式中的另一個類。替換類被稱為“偽裝成”目標類。

對於支援偽裝的版本,傳送到目標類所有訊息實際上都由偽裝類接收。

NSObject 包含 poseAsClass: 方法,該方法使我們能夠替換上面所說的現有類。

偽裝的限制

  • 一個類只能偽裝成其直接或間接超類之一。

  • 偽裝類不能定義目標類中不存在的任何新例項變數(儘管它可以定義或重寫方法)。

  • 目標類在偽裝之前可能沒有收到任何訊息。

  • 偽裝類可以透過 super 呼叫重寫的方法,從而整合目標類的實現。

  • 偽裝類可以重寫分類中定義的方法。

#import <Foundation/Foundation.h>

@interface MyString : NSString

@end

@implementation MyString

- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target
withString:(NSString *)replacement {
   NSLog(@"The Target string is %@",target);
   NSLog(@"The Replacement string is %@",replacement);
}

@end

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   [MyString poseAsClass:[NSString class]];
   NSString *string = @"Test";
   [string stringByReplacingOccurrencesOfString:@"a" withString:@"c"];
   
   [pool drain];
   return 0;
}

現在,當我們在較舊的 Mac OS X(V_10.5 或更早版本)中編譯並執行程式時,我們將獲得以下結果。

2013-09-22 21:23:46.829 Posing[372:303] The Target string is a
2013-09-22 21:23:46.830 Posing[372:303] The Replacement string is c

在上面的例子中,我們只是用我們的實現汙染了原始方法,這將影響所有使用上述方法的 NSString 操作。

Objective-C 擴充套件

類擴充套件與分類有些相似,但它只能新增到編譯時擁有原始碼的類(類與類擴充套件同時編譯)。

類擴充套件宣告的方法在原始類的實現塊中實現,因此例如,您不能在框架類(例如 Cocoa 或 Cocoa Touch 類,如 NSString)上宣告類擴充套件。

擴充套件實際上是沒有分類名稱的分類。它通常被稱為匿名分類

宣告擴充套件的語法使用 @interface 關鍵字,就像標準的 Objective-C 類描述一樣,但不表示從子類繼承。相反,它只是新增括號,如下所示:

@interface ClassName ()

@end

擴充套件的特性

  • 不能為任何類宣告擴充套件,只能為我們擁有原始實現原始碼的類宣告擴充套件。

  • 擴充套件是新增僅特定於類的私有方法和私有變數。

  • 在擴充套件中宣告的任何方法或變數即使對於繼承類也是不可訪問的。

擴充套件示例

讓我們建立一個具有擴充套件的 SampleClass 類。在擴充套件中,讓我們有一個私有變數 internalID。

然後,讓我們有一個 getExternalID 方法,它在處理 internalID 後返回 externalID。

示例如下所示,這在線上編譯器上不起作用。

#import <Foundation/Foundation.h>

@interface SampleClass : NSObject {
   NSString *name;
}

- (void)setInternalID;
- (NSString *)getExternalID;

@end

@interface SampleClass() {
   NSString *internalID;
}

@end

@implementation SampleClass

- (void)setInternalID {
   internalID = [NSString stringWithFormat: 
   @"UNIQUEINTERNALKEY%dUNIQUEINTERNALKEY",arc4random()%100];
}

- (NSString *)getExternalID {
   return [internalID stringByReplacingOccurrencesOfString: 
   @"UNIQUEINTERNALKEY" withString:@""];
}

@end

int main(int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass setInternalID];
   NSLog(@"ExternalID: %@",[sampleClass getExternalID]);        
   [pool drain];
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-22 21:18:31.754 Extensions[331:303] ExternalID: 51

在上面的示例中,我們可以看到 internalID 沒有直接返回。在這裡,我們刪除了 UNIQUEINTERNALKEY,並且只使剩餘的值可用於 getExternalID 方法。

上面的示例只使用了字串操作,但它可以具有許多功能,例如加密/解密等等。

Objective-C 協議

Objective-C 允許您定義協議,這些協議宣告特定情況下預期使用的方法。協議在符合協議的類中實現。

一個簡單的例子是一個網路 URL 處理類,它將有一個協議,其中包含諸如 processCompleted 委託方法之類的使用方法,該方法在網路 URL 獲取操作完成後通知呼叫類。

協議的語法如下所示。

@protocol ProtocolName
@required
// list of required methods
@optional
// list of optional methods
@end

@required 關鍵字下的方法必須在符合協議的類中實現,而@optional 關鍵字下的方法是可選實現的。

以下是符合協議的類的語法

@interface MyClass : NSObject <MyProtocol>
...
@end

這意味著 MyClass 的任何例項不僅將響應介面中專門宣告的方法,而且 MyClass 還為 MyProtocol 中的必需方法提供了實現。無需在類介面中重新宣告協議方法 - 採用協議就足夠了。

如果您需要一個類採用多個協議,您可以將它們指定為逗號分隔的列表。我們有一個委託物件,它儲存實現協議的呼叫物件的引用。

一個例子如下所示。

#import <Foundation/Foundation.h>

@protocol PrintProtocolDelegate
- (void)processCompleted;

@end

@interface PrintClass :NSObject {
   id delegate;
}

- (void) printDetails;
- (void) setDelegate:(id)newDelegate;
@end

@implementation PrintClass
- (void)printDetails {
   NSLog(@"Printing Details");
   [delegate processCompleted];
}

- (void) setDelegate:(id)newDelegate {
   delegate = newDelegate;
}

@end

@interface SampleClass:NSObject<PrintProtocolDelegate>
- (void)startAction;

@end

@implementation SampleClass
- (void)startAction {
   PrintClass *printClass = [[PrintClass alloc]init];
   [printClass setDelegate:self];
   [printClass printDetails];
}

-(void)processCompleted {
   NSLog(@"Printing Process Completed");
}

@end

int main(int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass startAction];
   [pool drain];
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-22 21:15:50.362 Protocols[275:303] Printing Details
2013-09-22 21:15:50.364 Protocols[275:303] Printing Process Completed

在上面的例子中,我們看到了委託方法是如何被呼叫和執行的。它從 startAction 開始,一旦程序完成,就會呼叫委託方法 processCompleted 來通知操作已完成。

在任何 iOS 或 Mac 應用中,程式的實現都離不開委託(delegate)。因此,理解委託的使用非常重要。為了避免記憶體洩漏,委託物件應該使用 `unsafe_unretained` 屬性型別。

Objective-C 動態繫結

動態繫結是在執行時而不是編譯時確定要呼叫的方法。動態繫結也稱為後期繫結。

在 Objective-C 中,所有方法都在執行時動態解析。執行的程式碼取決於方法名稱(選擇器)和接收物件。

動態繫結支援多型性。例如,考慮一個包含矩形和正方形的物件集合。每個物件都有其自己的 `printArea` 方法實現。

在下面的程式碼片段中,表示式 `[anObject printArea]` 應該執行的實際程式碼是在執行時確定的。執行時系統使用方法執行的選擇器來識別 `anObject` 所屬類的相應方法。

讓我們來看一個簡單的程式碼來解釋動態繫結。

#import <Foundation/Foundation.h>

@interface Square:NSObject {
   float area;
}

- (void)calculateAreaOfSide:(CGFloat)side;
- (void)printArea;
@end

@implementation Square
- (void)calculateAreaOfSide:(CGFloat)side {
   area = side * side;
}

- (void)printArea {
   NSLog(@"The area of square is %f",area);
}

@end

@interface Rectangle:NSObject {
   float area;
}

- (void)calculateAreaOfLength:(CGFloat)length andBreadth:(CGFloat)breadth;
- (void)printArea;
@end

@implementation  Rectangle

- (void)calculateAreaOfLength:(CGFloat)length andBreadth:(CGFloat)breadth {
   area = length * breadth;
}

- (void)printArea {
   NSLog(@"The area of Rectangle is %f",area);
}

@end

int main() {
   Square *square = [[Square alloc]init];
   [square calculateAreaOfSide:10.0];
   
   Rectangle *rectangle = [[Rectangle alloc]init];
   [rectangle calculateAreaOfLength:10.0 andBreadth:5.0];
   
   NSArray *shapes = [[NSArray alloc]initWithObjects: square, rectangle,nil];
   id object1 = [shapes objectAtIndex:0];
   [object1 printArea];
   
   id object2 = [shapes objectAtIndex:1];
   [object2 printArea];
   
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-28 07:42:29.821 demo[4916] The area of square is 100.000000
2013-09-28 07:42:29.821 demo[4916] The area of Rectangle is 50.000000

正如你在上面的例子中看到的,`printArea` 方法是在執行時動態選擇的。這是一個動態繫結的例子,在處理類似物件時非常有用。

Objective-C 組合物件

我們可以在類簇中建立一個子類,定義一個在其內部嵌入物件的類。這些類物件是組合物件。

所以你可能想知道什麼是類簇。所以我們首先看看什麼是類簇。

類簇

類簇是一種設計模式,Foundation 框架廣泛使用它。類簇將多個私有的具體子類組合在一個公共的抽象超類之下。這種類的分組簡化了面向物件框架的公共可見架構,而不會降低其功能豐富性。類簇基於抽象工廠設計模式。

簡單來說,與其為類似的功能建立多個類,不如建立一個單一類,根據輸入值來處理。

例如,在 `NSNumber` 中,我們有很多類簇,例如 `char`、`int`、`bool` 等等。我們將它們全部組合到一個類中,該類負責在一個類中處理類似的操作。`NSNumber` 實際上是將這些基本型別的數值包裝到物件中。

那麼組合物件到底是什麼呢?

透過在我們自己設計的物件中嵌入一個私有的簇物件,我們建立了一個組合物件。這個組合物件可以依靠簇物件實現其基本功能,只攔截組合物件想要以某種特定方式處理的訊息。這種架構減少了我們必須編寫的程式碼量,並允許你利用 Foundation 框架提供的經過測試的程式碼。

下圖對此進行了說明。

Objective-C Composite Objects

組合物件必須宣告自己是簇的抽象超類的子類。作為子類,它必須覆蓋超類的原始方法。它也可以覆蓋派生方法,但這並非必要,因為派生方法透過原始方法工作。

`NSArray` 類的 `count` 方法就是一個例子;介入物件的覆蓋方法的實現可以簡單到——

- (unsigned)count  {
   return [embeddedObject count];
}

在上面的例子中,嵌入的物件實際上是 `NSArray` 型別。

組合物件示例

現在為了看到一個完整的例子,讓我們看看下面來自 Apple 文件的例子。

#import <Foundation/Foundation.h>

@interface ValidatingArray : NSMutableArray {
   NSMutableArray *embeddedArray;
}

+ validatingArray;
- init;
- (unsigned)count;
- objectAtIndex:(unsigned)index;
- (void)addObject:object;
- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
- (void)removeLastObject;
- (void)insertObject:object atIndex:(unsigned)index;
- (void)removeObjectAtIndex:(unsigned)index;

@end

@implementation ValidatingArray
- init {
   self = [super init];
   if (self) {
      embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init];
   }
   return self;
}

+ validatingArray {
   return [[self alloc] init] ;
}

- (unsigned)count {
   return [embeddedArray count];
}

- objectAtIndex:(unsigned)index {
   return [embeddedArray objectAtIndex:index];
}

- (void)addObject:(id)object {
   if (object != nil) {
      [embeddedArray addObject:object];
   }
}

- (void)replaceObjectAtIndex:(unsigned)index withObject:(id)object; {
   if (index <[embeddedArray count] && object != nil) {
      [embeddedArray replaceObjectAtIndex:index withObject:object];
   }
}

- (void)removeLastObject; {
   if ([embeddedArray count] > 0) {
      [embeddedArray removeLastObject];
   }
}

- (void)insertObject:(id)object atIndex:(unsigned)index; {
   if (object != nil) {
      [embeddedArray insertObject:object atIndex:index];
   }
}

- (void)removeObjectAtIndex:(unsigned)index; {
   if (index <[embeddedArray count]) {
      [embeddedArray removeObjectAtIndex:index];
   }
}

@end

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   ValidatingArray *validatingArray = [ValidatingArray validatingArray];
   
   [validatingArray addObject:@"Object1"];
   [validatingArray addObject:@"Object2"];
   [validatingArray addObject:[NSNull null]];
   [validatingArray removeObjectAtIndex:2];
   NSString *aString = [validatingArray objectAtIndex:1];
   NSLog(@"The value at Index 1 is %@",aString);
   [pool drain];
   
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-28 22:03:54.294 demo[6247] The value at Index 1 is Object2

在上面的例子中,我們可以看到,驗證陣列的一個函式不允許新增空物件,這會在正常情況下導致崩潰。但是我們的驗證陣列會處理這個問題。類似地,驗證陣列中的每個方法除了正常的操作順序之外,還添加了驗證過程。

Obj-C Foundation 框架

如果你參考 Apple 文件,你可以看到 Foundation 框架的詳細資訊如下。

Foundation 框架定義了 Objective-C 類的一個基礎層。除了提供一組有用的原始物件類之外,它還引入了幾個範例,這些範例定義了 Objective-C 語言未涵蓋的功能。Foundation 框架的設計目標如下:

  • 提供一小組基本的實用程式類。

  • 透過引入諸如釋放之類的始終如一的約定來簡化軟體開發。

  • 支援 Unicode 字串、物件永續性和物件分發。

  • 提供一定程度的作業系統獨立性以增強可移植性。

該框架由 NeXTStep 開發,NeXTStep 被 Apple 收購,這些基礎類成為 Mac OS X 和 iOS 的一部分。

由於它是由 NeXTStep 開發的,所以它的類字首是“NS”。

我們在所有示例程式中都使用了 Foundation 框架。使用 Foundation 框架幾乎是必須的。

通常,我們使用類似 `#import ` 的語句來匯入 Objective-C 類,但是為了避免匯入太多的類,所有類都在 `#import ` 中匯入。

`NSObject` 是所有物件的基類,包括 Foundation Kit 類。它提供記憶體管理的方法。它還提供與執行時系統的基本介面以及充當 Objective-C 物件的能力。它沒有任何基類,並且是所有類的根。

基於功能的 Foundation 類

序號 迴圈型別和描述
1 資料儲存

`NSArray`、`NSDictionary` 和 `NSSet` 為任何類的 Objective-C 物件提供儲存。

2 文字和字串

`NSCharacterSet` 表示各種字元分組,`NSString` 和 `NSScanner` 類使用這些字元分組。`NSString` 類表示文字字串,並提供用於搜尋、組合和比較字串的方法。`NSScanner` 物件用於從 `NSString` 物件掃描數字和單詞。

3 日期和時間

`NSDate`、`NSTimeZone` 和 `NSCalendar` 類儲存時間和日期,並表示日曆資訊。它們提供計算日期和時間差的方法。它們與 `NSLocale` 一起,提供以多種格式顯示日期和時間以及根據世界位置調整時間和日期的方法。

4 異常處理

異常處理用於處理意外情況,它在 Objective-C 中透過 `NSException` 提供。

5 檔案處理

檔案處理是在 `NSFileManager` 類的幫助下完成的。

6 URL 載入系統

一組類和協議,提供對常見網際網路協議的訪問。

Objective-C 快速列舉

快速列舉是 Objective-C 的一個特性,它有助於列舉集合。所以為了瞭解快速列舉,我們需要先了解集合,這將在下一節中解釋。

Objective-C 中的集合

集合是基本結構。它用於儲存和管理其他物件。集合的全部目的是它提供了一種有效儲存和檢索物件的方法。

有幾種不同型別的集合。雖然它們都具有儲存其他物件的相同目的,但它們主要在檢索物件的方式上有所不同。Objective-C 中最常用的集合是:

  • `NSSet`
  • `NSArray`
  • `NSDictionary`
  • `NSMutableSet`
  • `NSMutableArray`
  • `NSMutableDictionary`

如果你想了解更多關於這些結構的資訊,請參考 Foundation 框架 中的資料儲存。

快速列舉語法

for (classType variable in collectionObject ) { 
  statements 
}

這是一個快速列舉的例子。

#import <Foundation/Foundation.h>

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   NSArray *array = [[NSArray alloc]
   initWithObjects:@"string1", @"string2",@"string3",nil];
   
   for(NSString *aString in array) {
      NSLog(@"Value: %@",aString);
   }
   
   [pool drain];
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-28 06:26:22.835 demo[7426] Value: string1
2013-09-28 06:26:22.836 demo[7426] Value: string2
2013-09-28 06:26:22.836 demo[7426] Value: string3

正如你看到的輸出那樣,陣列中的每個物件都是按順序列印的。

反向快速列舉

for (classType variable in [collectionObject reverseObjectEnumerator] ) { 
  statements 
}

這是一個在快速列舉中使用 `reverseObjectEnumerator` 的例子。

#import <Foundation/Foundation.h>

int main() {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   NSArray *array = [[NSArray alloc]
   initWithObjects:@"string1", @"string2",@"string3",nil];
   
   for(NSString *aString in [array reverseObjectEnumerator]) {
      NSLog(@"Value: %@",aString);
   }
   
   [pool drain];
   return 0;
}

現在,當我們編譯並執行程式時,我們將得到以下結果。

2013-09-28 06:27:51.025 demo[12742] Value: string3
2013-09-28 06:27:51.025 demo[12742] Value: string2
2013-09-28 06:27:51.025 demo[12742] Value: string1

正如你看到的輸出那樣,陣列中的每個物件都被打印出來,但是與正常的快速列舉相比,順序是相反的。

Obj-C 記憶體管理

記憶體管理是任何程式語言中最重要的一環。它是當需要物件時分配物件記憶體,當不再需要物件時釋放物件記憶體的過程。

管理物件記憶體是一個性能問題;如果應用程式不釋放不需要的物件,其記憶體佔用量會增加,從而影響效能。

Objective-C 記憶體管理技術大致可以分為兩種型別。

  • “手動保留-釋放”(MRR)
  • “自動引用計數”(ARC)

“手動保留-釋放”(MRR)

在 MRR 中,我們透過自己跟蹤物件來顯式地管理記憶體。這是使用一種稱為引用計數的模型實現的,Foundation 類 `NSObject` 與執行時環境一起提供。

MRR 和 ARC 之間的唯一區別在於,前者由我們手動處理保留和釋放,而後者則自動處理。

下圖顯示了 Objective-C 中記憶體管理的工作方式示例。

Objective-C Memory management

上圖顯示了類 A 物件的記憶體生命週期。如你所見,保留計數顯示在物件下方,當物件的保留計數變為 0 時,物件將被完全釋放,其記憶體將被釋放供其他物件使用。

類 A 物件首先使用 `NSObject` 中提供的 `alloc/init` 方法建立。現在,保留計數變為 1。

現在,類 B 保留類 A 的物件,類 A 物件的保留計數變為 2。

然後,類 C 複製該物件。現在,它被建立為類 A 的另一個例項,例項變數的值相同。這裡,保留計數為 1,而不是原始物件的保留計數。圖中虛線表示這一點。

複製的物件由類 C 使用 `release` 方法釋放,保留計數變為 0,因此物件被銷燬。

對於初始的類 A 物件,保留計數為 2,必須釋放兩次才能將其銷燬。這是透過類 A 和類 B 的 `release` 語句完成的,它們分別將保留計數遞減到 1 和 0。最終,物件被銷燬。

MRR 基本規則

  • 我們擁有我們建立的任何物件:我們使用名稱以“alloc”、“new”、“copy”或“mutableCopy”開頭的方法建立物件

  • 我們可以使用 `retain` 來獲取物件的擁有權:接收到的物件通常保證在其接收到的方法內保持有效,該方法也可以安全地將物件返回給其呼叫者。我們在兩種情況下使用 `retain`:

    • 在訪問器方法或 `init` 方法的實現中,獲取我們想要作為屬性值儲存的物件的所有權。

    • 為了防止物件作為其他操作的副作用而失效。

  • 當我們不再需要它時,我們必須放棄我們擁有的物件的擁有權:我們透過向物件傳送 `release` 訊息或 `autorelease` 訊息來放棄物件的擁有權。在 Cocoa 術語中,放棄物件的擁有權通常被稱為“釋放”物件。

  • 你不得放棄你不擁有的物件的擁有權:這只是前面明確說明的策略規則的推論。

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
  [super dealloc];
}

@end

int main() {
   
   /* my first program in Objective-C */
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass sampleMethod];
   
   NSLog(@"Retain Count after initial allocation: %d", 
   [sampleClass retainCount]);
   [sampleClass retain];
   
   NSLog(@"Retain Count after retain: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"Retain Count after release: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"SampleClass dealloc will be called before this");
   
   // Should set the object to nil
   sampleClass = nil;
   return 0;
}

編譯上面的程式時,我們將得到以下輸出。

2013-09-28 04:39:52.310 demo[8385] Hello, World!
2013-09-28 04:39:52.311 demo[8385] Retain Count after initial allocation: 1
2013-09-28 04:39:52.311 demo[8385] Retain Count after retain: 2
2013-09-28 04:39:52.311 demo[8385] Retain Count after release: 1
2013-09-28 04:39:52.311 demo[8385] Object deallocated
2013-09-28 04:39:52.311 demo[8385] SampleClass dealloc will be called before this

“自動引用計數”(ARC)

在自動引用計數 (ARC) 中,系統使用與 MRR 相同的引用計數系統,但它會在編譯時為我們插入適當的記憶體管理方法呼叫。我們強烈建議對新專案使用 ARC。如果我們使用 ARC,通常不需要理解本文件中描述的底層實現,儘管在某些情況下這可能會有所幫助。有關 ARC 的更多資訊,請參閱 遷移到 ARC 發行說明。

如上所述,在 ARC 中,我們不需要新增 `release` 和 `retain` 方法,因為編譯器將負責處理這些方法。實際上,Objective-C 的底層過程仍然相同。它在內部使用 `retain` 和 `release` 操作,使開發人員更容易編寫程式碼,而無需擔心這些操作,這將減少編寫的程式碼量和記憶體洩漏的可能性。

還有一個叫做垃圾回收的機制,它曾與MRR一起在Mac OS-X中使用,但自從OS-X Mountain Lion中棄用後,便不再與MRR一起討論。此外,iOS物件從未有過垃圾回收功能。並且在ARC下,OS-X中也不再使用垃圾回收。

這裡是一個簡單的ARC示例。請注意,這在線上編譯器上無法執行,因為它不支援ARC。

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
}

@end

int main() {
   /* my first program in Objective-C */
   @autoreleasepool {
      SampleClass *sampleClass = [[SampleClass alloc]init];
      [sampleClass sampleMethod];
      sampleClass = nil;
   }
   return 0;
}

編譯上面的程式時,我們將得到以下輸出。

2013-09-28 04:45:47.310 demo[8385] Hello, World!
2013-09-28 04:45:47.311 demo[8385] Object deallocated
廣告