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://gnu.ac.cn/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
广告
© . All rights reserved.