目录

Objective-C - 快速指南

Objective-C Overview

Objective-C是通用语言,它是在C语言编程语言之上开发的,它增加了Small Talk编程语言的功能,使其成为面向对象的语言。 它主要用于开发iOS和Mac OS X操作系统及其应用程序。

最初,Objective-C是由NeXT为其NeXTSTEP操作系统开发的,苹果公司为其iOS和Mac OS X接管了它。

Object-Oriented Programming

完全支持面向对象的编程,包括面向对象开发的四大支柱 -

  • Encapsulation
  • 数据隐藏
  • Inheritance
  • Polymorphism

示例代码

#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
   NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   NSLog (@"hello world");
   [pool drain];
   return 0;
}

基础框架

Foundation Framework提供了大量功能,如下所示。

  • 它包括一个扩展数据类型列表,如NSArray,NSDictionary,NSSet等。

  • 它由一组丰富的函数组成,用于处理文件,字符串等。

  • 它提供了URL处理功能,日期格式化,数据处理,错误处理等实用程序。

Learning Objective-C

学习Objective-C时最重要的事情是专注于概念,而不是迷失在语言技术细节中。

学习编程语言的目的是成为一个更好的程序员; 也就是说,在设计和实施新系统以及维护旧系统方面变得更加有效。

使用Objective-C

如前所述,Objective-C用于iOS和Mac OS X.它拥有大量iOS用户,并且大大增加了Mac OS X用户。 因为Apple首先关注质量,而且对于那些开始学习Objective-C的人来说非常棒。

Objective-C Environment Setup

本地环境设置 (Local Environment Setup)

如果您仍然愿意为Objective-C编程语言设置环境,则需要在计算机上使用以下两个软件:(a)文本编辑器和(b)GCC编译器。

文本编辑器 (Text Editor)

这将用于键入您的程序。 少数编辑器的示例包括Windows Notepad,OS Edit命令,Brief,Epsilon,EMACS和vim或vi。

文本编辑器的名称和版本可能因不同的操作系统而异。 例如,Notepad将在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。 这可以通过从http://main.gnustep.org/resources/downloads.php下载软件包来完成。

现在,我们需要切换到下载的文件夹并通过以下方式解压缩文件 -

$ tar xvfz gnustep-startup-<version>.tar.gz
</version>

现在,我们需要切换到使用 - 创建的文件夹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 iowiki[12906] hello world

在Mac OS上安装

如果您使用的是Mac OS X,获取GCC的最简单方法是从Apple的网站下载Xcode开发环境,并按照简单的安装说明进行操作。 一旦设置了Xcode,就可以将GNU编译器用于C/C ++。

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

Installation on Windows

为了在Windows上运行Objective-C程序,我们需要安装MinGW和GNUStep Core。 两者都可以在https://www.gnu.org/software/gnustep/windows/installer.html

首先,我们需要安装MSYS/MinGW System软件包。 然后,我们需要安装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 iowiki[1200] hello world

Objective-C Program Structure

在我们研究Objective-C编程语言的基本构建块之前,让我们看一下最基本的Objective-C程序结构,以便我们在接下来的章节中将其作为参考。

Objective-C Hello World示例

Objective-C程序基本上由以下部分组成 -

  • 预处理程序命令
  • Interface
  • Implementation
  • Method
  • Variables
  • 声明和表达
  • Comments

让我们看一下打印“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 《Foundation/Foundation.h》的第一行是一个预处理程序命令,它告诉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 Basic Syntax

您已经看到了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;

注释 (Comments)

注释就像帮助Objective-C程序中的文本一样,编译器会忽略它们。 它们以/ *开头并以字符* /结尾,如下所示 -

/* my first program in Objective-C */

您不能在注释中添加注释,并且它们不会出现在字符串或字符文字中。

标识符 (Identifiers)

Objective-C标识符是用于标识变量,函数或任何其他用户定义项的名称。 标识符以字母A到Z或a到z或下划线_开头,后跟零个或多个字母,下划线和数字(0到9)。

Objective-C不允许标识符中的标点符号,如@,$和%。 Objective-C是一种case-sensitive编程语言。 因此, Manpowermanpower是Objective-C中的两个不同的标识符。 以下是可接受标识符的一些示例 -

mohd       zara    abc   move_name  a_123
myname50   _temp   j     a23b9      retVal

关键字 (Keywords)

以下列表显示了Objective-C中的一些保留字。 这些保留字不能用作常量或变量或任何其他标识符名称。

autoelselongswitch
breakenumregistertypedef
caseexternreturnunion
charfloatshortunsigned
constforsignedvoid
continuegotosizeofvolatile
defaultifstaticwhile
dointstruct_Packed
doubleprotocolinterfaceimplementation
NSObjectNSIntegerNSNumberCGFloat
propertynonatomic;retainstrong
weakunsafe_unretained;readwritereadonly

Objective-C中的空白

只包含空格(可能带有注释)的行称为空行,而Objective-C编译器完全忽略它。

Whitespace是Objective-C中用于描述空格,制表符,换行符和注释的术语。 空格将语句的一部分与另一部分分开,并使编译器能够识别语句中的一个元素(如int)的结束位置以及下一个元素的开始位置。 因此,在以下声明中 -

int age;

在int和age之间必须至少有一个空格字符(通常是空格),以便编译器能够区分它们。 另一方面,在以下声明中,

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

水果和=之间,或=和苹果之间不需要空白字符,但如果您希望出于可读性目的,可以自由添加一些空白字符。

Objective-C Data Types

在Objective-C编程语言中,数据类型是指用于声明不同类型的变量或函数的扩展系统。 变量的类型决定了它在存储中占用的空间大小以及如何解释存储的位模式。

Objective-C中的类型可分为以下几类 -

Sr.No. 类型和描述
1

Basic Types −

它们是算术类型,由两种类型组成:(a)整数类型和(b)浮点类型。

2

Enumerated types −

它们又是算术类型,它们用于定义只能在整个程序中分配某些离散整数值的变量。

3

The type void −

类型说明符void表示没有可用的值。

4

Derived types −

它们包括(a)指针类型,(b)数组类型,(c)结构类型,(d)联合类型和(e)函数类型。

数组类型和结构类型统称为聚合类型。 函数的类型指定函数返回值的类型。 我们将在下一节中看到基本类型,而其他类型将在后续章节中介绍。

整数类型 (Integer Types)

下表提供了有关其存储大小和值范围的标准整数类型的详细信息 -

类型 存储大小 价值范围
char 1个字节 -128到127或0到255
unsigned char 1个字节 0到255
signed char 1个字节 -128 to 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 to 32,767
unsigned short 2个字节 0 to 65,535
long 4字节 -2,147,483,648 to 2,147,483,647
unsigned long 4字节 0 to 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 

Floating-Point Types

下表提供了有关存储大小和值范围及其精度的标准浮点类型的详细信息 -

类型 存储大小 价值范围 精确
float 4个字节 1.2E-38至3.4E + 38 6 decimal places
double 8个字节 2.3E-308至1.7E + 308 15 decimal places
long double 10个字节 3.4E-4932至1.1E + 4932 19 decimal places

头文件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类型指定没有可用的值。 它用于三种情况 -

Sr.No. 类型和描述
1 Function returns as void

Objective-C中有各种函数,它们不返回值,或者你可以说它们返回void。 没有返回值的函数的返回类型为void。 例如, void exit (int status);

2 Function arguments as void

Objective-C中有各种函数不接受任何参数。 没有参数的函数可以作为void接受。 例如, int rand(void);

此时可能无法理解void类型,所以让我们继续,我们将在后面的章节中介绍这些概念。

Objective-C Variables

变量只不过是我们的程序可以操作的存储区域的名称。 Objective-C中的每个变量都有一个特定的类型,它决定了变量内存的大小和布局; 可存储在该内存中的值范围; 以及可以应用于变量的操作集。

变量的名称可以由字母,数字和下划线字符组成。 它必须以字母或下划线开头。 大写和小写字母是不同的,因为Objective-C区分大小写。 根据前一章解释的基本类型,将有以下基本变量类型 -

Sr.No. 类型和描述
1

char

通常是一个八位字节(一个字节)。 这是一个整数类型。

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程序中多次声明变量,但它只能在文件,函数或代码块中定义一次。

例子 (Example)

尝试以下示例,其中变量已在顶部声明,但它们已在主函数内定义和初始化 -

#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中有两种表达式 -

  • lvalue - 引用内存位置的表达式称为“左值”表达式。 左值可以显示为赋值的左侧或右侧。

  • rvalue - 术语rvalue是指存储在内存中某个地址的数据值。 rvalue是一个不能赋值给它的表达式,这意味着rvalue可能出现在赋值的右边但不是左边。

变量是左值,因此可能出现在赋值的左侧。 数字文字是右值,因此可能无法分配,也不能出现在左侧。 以下是有效的声明 -

int g = 20;

但是以下不是有效的语句,会产生编译时错误 -

10 = 20;

Objective-C Constants

常量指的是程序在执行期间不会改变的固定值。 这些固定值也称为literals

常量可以是任何基本数据类型,如an integer constant, a floating constant, a character constant, or a string literal 。 还有枚举常量。

constants被视为常规变量,除了它们的值在定义后无法修改。

整数文字

整数文字可以是十进制,八进制或十六进制常量。 前缀指定基数或基数:十六进制为0x或0X,八进制为0,十进制为空。

整数文字也可以有一个后缀,它是U和L的组合,分别对于unsigned和long。 后缀可以是大写或小写,可以按任何顺序排列。

以下是整数文字的一些示例 -

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)。 在这里,您有一些此类转义序列代码的列表 -

逃脱序列 含义
\\ \字符
\' ' character(字符)
\" " character(字符)
\? ? 字符
\aAlert or bell
\bBackspace
\fForm feed
\nNewline
\rCarriage return
\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预处理器 (The #define Preprocessor)

以下是使用#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] 

请注意,在CAPITALS中定义常量是一种很好的编程习惯。

Objective-C Operators

运算符是一个符号,告诉编译器执行特定的数学或逻辑操作。 Objective-C语言内置运算符丰富,并提供以下类型的运算符 -

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 按位运算符
  • 分配运算符
  • 其它运算符

本教程将逐一解释算术,关系,逻辑,按位,赋值和其他运算符。

算术运算符 (Arithmetic Operators)

下表显示了Objective-C语言支持的所有算术运算符。 假设变量A保持10,变量B保持20,则 -

显示示例

操作者 描述
+ 添加两个操作数 A + B将给出30
- 从第一个减去第二个操作数 A - B将给-10
* 将两个操作数相乘 A * B将给出200
/Divides numerator by denominator B/A会给2
% 模数运算符和整数除法后的余数 B%A将给出0
++ 递增运算符将整数值增加1 A ++将给出11
-- 递减运算符将整数值减1 A-- will give 9

关系运算符 (Relational Operators)

下表显示了Objective-C语言支持的所有关系运算符。 假设变量A保持10,变量B保持20,则 -

显示示例

操作者 描述
== 检查两个操作数的值是否相等; 如果是,则条件成立。 (A == B)不是真的。
!= 检查两个操作数的值是否相等; 如果值不相等,则条件成立。 (A!= B)是真的。
> 检查左操作数的值是否大于右操作数的值; 如果是,则条件成立。 (A> B)不是真的。
< 检查左操作数的值是否小于右操作数的值; 如果是,则条件成立。 (A < B) 为真
>= 检查左操作数的值是否大于或等于右操作数的值; 如果是,则条件成立。 (A> = B)不是真的。
<= 检查左操作数的值是否小于或等于右操作数的值; 如果是,则条件成立。 (A <= B)是真的。

逻辑运算符 (Logical Operators)

下表显示了Objective-C语言支持的所有逻辑运算符。 假设变量A保持1而变量B保持0,则 -

显示示例

操作者 描述
&& 称为逻辑AND运算符。 如果两个操作数都不为零,则条件成立。 (A && B)是假的。
|| 称为逻辑OR运算符。 如果两个操作数中的任何一个不为零,则条件变为真。 (A || B)是真的。
! 称为逻辑非运算符。 用于反转其操作数的逻辑状态。 如果条件为真,则Logical NOT运算符将为false。 !(A && B)是真的。

按位运算符 (Bitwise Operators)

按位运算符处理位并执行逐位运算。 &,|和^的真值表如下 -

p q p&q p | q p ^ q
00000
01011
11110
10011

假设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语言支持的Bitwise运算符如下表所示。 假设变量A保持60,变量B保持13然后 -

显示示例

操作者 描述
& 如果二进制AND运算符存在于两个操作数中,则它会将结果复制到结果中。 (A&B)将给出12,即0000 1100
| 二进制OR运算符如果存在于任一操作数中,则复制一位。 (A | B)将给出61,即0011 1101
^ 二进制异或运算符如果在一个操作数中设置但不在两个操作数中设置,则复制该位。 (A ^ B)将给出49,即0011 0001
~ 二元一元补语运算符是一元的,具有“翻转”位的效果。 (~A)将给出-61,即2的补码形式的1100 0011。
<< 二进制左移运算符。 左操作数值向左移动右操作数指定的位数。 A << 2将给出240,即1111 0000
>> 二进制右移运算符。 左操作数值向右移动右操作数指定的位数。 A >> 2将给出15,即0000 1111

赋值操作符 (Assignment Operators)

Objective-C语言支持以下赋值运算符 -

显示示例

操作者 描述
= 简单赋值运算符,将右侧操作数的值分配给左侧操作数 C = A + B将A + B的值分配给C
+= 添加AND赋值运算符,它将右操作数添加到左操作数并将结果赋给左操作数 C + = A等于C = C + A.
-= 减去AND赋值运算符,它从左操作数中减去右操作数,并将结果赋给左操作数 C - = A相当于C = C - A.
*= 乘以AND赋值运算符,它将右操作数与左操作数相乘,并将结果赋给左操作数 C * = A等于C = C * A.
/= 除以AND赋值运算符,它将左操作数除以右操作数,并将结果赋给左操作数 C/= A相当于C = C/A.
%= 模数AND赋值运算符,它使用两个操作数获取模数,并将结果赋给左操作数 C%= A等于C = C%A
<<= 左移AND赋值运算符 C << = 2与C = C << 2相同
>>= 右移AND赋值运算符 C >> = 2与C = C >> 2相同
&= 按位AND赋值运算符 C&= 2与C = C&2相同
^= 按位异或和赋值运算符 C ^ = 2与C = C ^ 2相同
|= 按位包含OR和赋值运算符 C | = 2与C = C |相同 2

其他运算符↦sizeof&trinary

还有其他一些重要的运算符,包括sizeof? : ? : Objective-C语言支持。

显示示例

操作者 描述
sizeof() 返回变量的大小。 sizeof(a),其中a是整数,将返回4。
& 返回变量的地址。 &一个; 将给出变量的实际地址。
*Pointer to a variable. *一个; 将指向变量。
? : 条件表达式 如果条件为真? 然后是值X:否则为Y值

Objective-C中的运算符优先级

运算符优先级确定表达式中的术语分组。 这会影响表达式的计算方式。 某些运算符的优先级高于其他运算符; 例如,乘法运算符的优先级高于加法运算符 -

例如,x = 7 + 3 * 2; 这里,x被赋值为13,而不是20,因为operator *的优先级高于+,所以它首先乘以3 * 2然后加到7中。

此处,具有最高优先级的运算符显示在表的顶部,具有最低优先级的运算符显示在底部。 在表达式中,将首先评估更高优先级的运算符。

类别 操作者 关联性
Postfix  ()[] - >。 ++ - - 左到右
Unary  + - ! 〜++ - - (类型)*&sizeof 右到左
Multiplicative   * /% 左到右
Additive   + - 左到右
Shift   << >> 左到右
Relational   << => = 左到右
Equality   ==!= 左到右
Bitwise XOR  左到右
Bitwise OR  左到右
Logical AND &&  左到右
Logical OR ||  左到右
Conditional ?:  右到左
Assignment  = + = - = * =/=%= >> = << =&= ^ = | = 右到左
Comma  左到右

Objective-C Loops

当您需要多次执行代码块时,可能会出现这种情况。 通常,语句按顺序执行:首先执行函数中的第一个语句,然后执行第二个语句,依此类推。

编程语言提供各种控制结构,允许更复杂的执行路径。

循环语句允许我们多次执行语句或语句组,以下是大多数编程语言中循环语句的一般形式 -

循环架构

Objective-C编程语言提供以下类型的循环来处理循环需求。 单击以下链接以查看其详细信息。

Sr.No. 循环类型和描述
1 循环

在给定条件为真时重复语句或语句组。 它在执行循环体之前测试条件。

2 for循环

多次执行一系列语句,并缩写管理循环变量的代码。

3 做... while循环

像while语句一样,除了它测试循环体末尾的条件。

4 嵌套循环

您可以在任何其他循环内使用一个或多个循环,而for或do..while循环。

循环控制语句 (Loop Control Statements)

循环控制语句将执行从其正常序列更改。 当执行离开作用域时,将销毁在该作用域中创建的所有自动对象。

Objective-C支持以下控制语句。 单击以下链接以查看其详细信息。

Sr.No. 控制声明和描述
1 break statement

终止loopswitch语句,并在循环或切换后立即将执行转移到语句。

2 continue statement

导致循环跳过其身体的其余部分,并在重复之前立即重新测试其状态。

无限循环 (The Infinite Loop)

如果条件永远不会变为假,则循环变为无限循环。 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

决策结构要求程序员指定一个或多个要由程序评估或测试的条件,以及在条件被确定为真时要执行的一个或多个语句,以及可选的,如果条件要执行的其他语句被认定是假的。

以下是大多数编程语言中常见决策结构的一般形式 -

Objective-C中的决策声明

Objective-C编程语言将任何non-zeronon-null值假定为true ,如果它zeronull ,则将其假定为false值。

Objective-C编程语言提供以下类型的决策制定语句。 单击以下链接查看其详细信息 -

Sr.No. 声明和说明
1 if 语句

if statement由布尔表达式后跟一个或多个语句组成。

2 if...else 语句

if statement后面可以跟一个可选的else statement ,该else statement在布尔表达式为false时执行。

3 嵌套if语句

您可以在另一个ifelse if语句中使用ifelse if语句。

4 switch 语句

switch语句允许测试变量与值列表的相等性。

5 嵌套的switch语句

您可以在另一个switch语句中使用一个switch语句。

(The ? : Operator)

我们覆盖了conditional operator ? : conditional operator ? :在前一章中可以用来替换if...else语句。 它有以下一般形式 -

Exp1 ? Exp2 : Exp3;

Exp1,Exp2和Exp3是表达式。 注意结肠的使用和放置。

一个值? 表达式的确定方式如下:评估Exp1。 如果是,那么Exp2会被评估并成为整个值吗? 表达。 如果Exp1为false,则计算Exp3,其值将成为表达式的值。

Objective-C 函数

函数是一组一起执行任务的语句。 每个Objective-C程序都有一个C函数,它是main() ,所有最简单的程序都可以定义其他函数。

您可以将代码划分为单独的函数。 你如何在不同的函数之间划分你的代码取决于你,但逻辑上这个划分通常是这样,每个函数执行一个特定的任务。

函数declaration告诉编译器函数的名称,返回类型和参数。 函数definition提供函数的实际主体。

基本上在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编程语言中的方法定义由method headermethod body 。 以下是方法的所有部分 -

  • Return Type - 方法可以返回值。 return_type是函数返回的值的数据类型。 某些方法执行所需的操作而不返回值。 在这种情况下,return_type是关键字void

  • Method Name - 这是Method Name的实际名称。 方法名称和参数列表一起构成方法签名。

  • Arguments - 参数就像一个占位符。 调用函数时,将值传递给参数。 该值称为实际参数或参数。 参数列表指的是方法的参数的类型,顺序和数量。 参数是可选的; 也就是说,方法可能不包含任何参数。

  • Joining Argument - 一个加入的参数是让它更易于阅读并在调用时清楚地表达它。

  • Method Body - 方法体包含一组语句,用于定义方法的作用。

例子 (Example)

以下是名为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; 
}

方法声明

方法declaration告诉编译器有关函数名称以及如何调用该方法的信息。 函数的实际主体可以单独定义。

方法声明包含以下部分 -

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

对于上面定义的函数max(),以下是方法声明 -

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

在一个源文件中定义方法并在另一个文件中调用该方法时,需要方法声明。 在这种情况下,您应该在调用该函数的文件顶部声明该函数。

调用方法

在创建Objective-C方法时,您可以定义函数必须执行的操作。 要使用方法,您必须调用该函数来执行定义的任务。

当程序调用函数时,程序控制将转移到被调用的方法。 被调用的方法执行已定义的任务,当执行其返回语句或达到其函数结束右括号时,它将程序控制返回给主程序。

要调用方法,只需要传递必需的参数和方法名称,如果方法返回值,则可以存储返回的值。 例如 -

#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

函数参数(Function Arguments)

如果函数是使用参数,它必须声明接受参数值的变量。 这些变量称为函数的formal parameters

形式参数的行为与函数内部的其他局部变量相似,并在进入函数时创建,并在退出时销毁。

在调用函数时,有两种方法可以将参数传递给函数 -

Sr.No. 通话类型和说明
1 call by value [值传递]

此方法将参数的实际值复制到函数的形式参数中。 在这种情况下,对函数内部参数所做的更改不会对参数产生影响。

2 call by reference [引用传递]

此方法将参数的地址复制到形式参数中。 在函数内部,该地址用于访问调用中使用的实际参数。 这意味着对参数所做的更改会影响参数。

默认情况下,Objective-C使用call by value来传递参数。 通常,这意味着函数内的代码不能改变用于调用函数的参数,而上述示例在调用max()函数时使用相同的方法。

Objective-C Blocks

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的简单示例。 请注意,此示例doesn't work用于online compiler 。 使用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 Numbers

在Objective-C编程语言中,为了以对象形式保存基本数据类型,如int,float,bool,

Objective-C提供了一系列与NSNumber一起使用的方法,重要的方法列在下表中。

Sr.No. 方法和描述
1

+ (NSNumber *)numberWithBool:(BOOL)value

创建并返回包含给定值的NSNumber对象,将其视为BOOL。

2

+ (NSNumber *)numberWithChar:(char)value

创建并返回包含给定值的NSNumber对象,将其视为signed char。

3

+ (NSNumber *)numberWithDouble:(double)value

创建并返回包含给定值的NSNumber对象,将其视为double。

4

+ (NSNumber *)numberWithFloat:(float)value

创建并返回包含给定值的NSNumber对象,将其视为float。

5

+ (NSNumber *)numberWithInt:(int)value

创建并返回包含给定值的NSNumber对象,将其视为signed int。

6

+ (NSNumber *)numberWithInteger:(NSInteger)value

创建并返回包含给定值的NSNumber对象,将其视为NSInteger。

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 Arrays

Objective-C编程语言提供了一种称为the array的数据结构,它可以存储相同类型元素的固定大小顺序集合。 数组用于存储数据集合,但将数组视为相同类型的变量集合通常更有用。

您可以声明一个数组变量(例如数字)并使用数字[0],数字[1]和...,数字[99]来表示单个变量,例如number0,number1,...和number99,而不是声明单个变量。个别变数。 索引访问数组中的特定元素。

所有阵列都包含连续的内存位置。 最低地址对应于第一个元素,最高地址对应于最后一个元素。

Objective-C中的数组

声明数组 (Declaring Arrays)

要在Objective-C中声明一个数组,程序员指定元素的类型和数组所需的元素数量,如下所示 -

type arrayName [ arraySize ];

这称为一single-dimensional数组。 arraySize必须是大于零的整数常量, type可以是任何有效的Objective-C数据类型。 例如,要声明一个名为balance double类型的10元素数组,请使用此语句 -

double balance[10];

现在, balance是一个变量数组,足以容纳10个双数。

初始化数组 (Initializing Arrays)

您可以逐个初始化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赋值为50.0。 具有第四个索引的数组将是第5个,即最后一个元素,因为所有数组都将0作为其第一个元素的索引,也称为基本索引。 以下是我们上面讨论的相同阵列的图形表示 -

阵列演示

访问数组元素 (Accessing Array Elements)

通过索引数组名称来访问元素。 这是通过将元素的索引放在数组名称后面的方括号中来完成的。 例如 -

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 Arrays详细介绍

数组对Objective-C很重要,需要更多细节。 以下几个与数组相关的重要概念应该对Objective-C程序员清楚 -

Sr.No. 概念与描述
1 多维数组

Objective-C支持多维数组。 多维数组的最简单形式是二维数组。

2 将数组传递给函数

您可以通过指定不带索引的数组名称来向函数传递指向数组的指针。

3 从函数返回数组

Objective-C允许函数返回一个数组。

4 指向数组的指针

您只需指定数组名称即可生成指向数组第一个元素的指针,而无需任何索引。

Objective-C Pointers

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

所以,你了解什么是内存地址以及如何访问它,因此概念的基础已经结束。 现在让我们看看什么是指针。

什么是指针?

pointer是一个变量,其值是另一个变量的地址,即存储器位置的直接地址。 与任何变量或常量一样,必须先声明指针,然后才能使用它来存储任何变量地址。 指针变量声明的一般形式是 -

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的指针称为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具有特殊意义; 它表示指针不是指向可访问的内存位置。 但按照惯例,如果指针包含null(零)值,则假定它指向任何内容。

要检查空指针,可以使用if语句,如下所示 -

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

Objective-C指针详解

指针有许多但很简单的概念,它们对Objective-C编程非常重要。 以下几个重要的指针概念,对于Objective-C程序员来说应该是清楚的 -

Sr.No. 概念与描述
1 Objective-C - 指针算法

可以在指针上使用四个算术运算符:++, - ,+, -

2 Objective-C - 指针数组

您可以定义数组以包含许多指针。

3 Objective-C - 指针指针

Objective-C允许您在指针上指针等等。

4 将指针传递给Objective-C中的函数

通过引用或地址传递参数都可以通过被调用函数在调用函数中更改传递的参数。

5 从Objective-C中的函数返回指针

Objective-C允许函数返回指向局部变量,静态变量和动态分配内存的指针。

Objective-C Strings

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支持多种操作字符串的方法 -

Sr.No. 方法和目的
1

- (NSString *)capitalizedString;

返回接收器的大写表示。

2

- (unichar)characterAtIndex:(NSUInteger)index;

返回给定数组位置的字符。

3

- (double)doubleValue;

以double形式返回接收者文本的浮点值。

4

- (float)floatValue;

以float形式返回接收者文本的浮点值。

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 Structures

Objective-C数组允许您定义可以包含多个相同类型的数据项的变量类型,但structure是Objective-C编程中可用的另一个用户定义数据类型,它允许您组合不同类型的数据项。

结构用于表示记录,假设您想要在库中跟踪您的书籍。 您可能希望跟踪每本书的以下属性 -

  • Title
  • Author
  • Subject
  • 书名

定义一个结构 (Defining a Structure)

要定义结构,必须使用struct语句。 struct语句定义一个新的数据类型,为您的程序提供多个成员。 struct语句的格式是这样的 -

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

structure tag是可选的,每个成员定义是一个普通的变量定义,例如int i; 或浮动f; 或任何其他有效的变量定义。 在结构定义的最后,在最后一个分号之前,您可以指定一个或多个结构变量,但它是可选的。 以下是您声明Book结构的方式 -

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

访问结构成员 (Accessing Structure Members)

要访问结构的任何成员,我们使用member access operator (.) 。 成员访问运算符被编码为结构变量名称和我们希望访问的结构成员之间的句点。 您可以使用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

作为函数参数的结构 (Structures as Function Arguments)

您可以将结构作为函数参数传递,与传递任何其他变量或指针的方式非常相似。 您将以与上面示例中访问过的方式类似的方式访问结构变量 -

#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允许我们通过在变量之后放置:位长度来在结构定义中执行此操作。 例如 -

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 Preprocessors

Objective-C Preprocessor不是编译器的一部分,而是编译过程中的一个单独步骤。 简单来说,Objective-C预处理器只是一个文本替换工具,它指示编译器在实际编译之前进行必要的预处理。 我们将Objective-C预处理器称为OCPP。

所有预处理器命令都以井号(#)开头。 它必须是第一个非空白字符,并且为了便于阅读,预处理程序指令应该从第一列开始。 以下部分列出了所有重要的预处理程序指令 -

Sr.No. 指令和说明
1

#define

替换预处理器宏

2

#include

从另一个文件插入特定标头

3

#undef

取消定义预处理器宏

4

#ifdef

如果定义了此宏,则返回true

5

#ifndef

如果未定义此宏,则返回true

6

#if

测试编译时条件是否为真

7

#else

#if的替代方案

8

#elif

#else一个语句中的#if

9

#endif

结束预处理器条件

10

#error

在stderr上打印错误消息

11

#pragma

使用标准化方法向编译器发出特殊命令

预处理器示例

分析以下示例以了解各种指令。

#define MAX_ARRAY_LENGTH 20

该指令告诉OCPP用20替换MAX_ARRAY_LENGTH的实例。使用#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,则执行包含语句的过程。 如果在编译时将-DDEBUG标志传递给gcc编译器,这将非常有用。 这将定义DEBUG,因此您可以在编译期间动态打开和关闭调试。

预定义的宏

ANSI C定义了许多宏。 虽然每个都可用于编程,但不应直接修改预定义的宏。

Sr.No. 宏观和描述
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")

Stringize (#)

stringize或数字符号运算符('#')在宏定义中使用时,将宏参数转换为字符串常量。 此运算符只能在具有指定参数或参数列表的宏中使用。 例如 -

#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!

Token Pasting (##)

宏定义中的令牌粘贴运算符(##)组合了两个参数。 它允许将宏定义中的两个单独的标记连接到一个标记中。 例如 -

#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);

此示例显示令牌## n到token34的串联,这里我们使用了stringizetoken-pasting pasting。

defined()运算符

预处理器defined运算符用于常量表达式,以确定是否使用#define定义标识符。 如果定义了指定的标识符,则该值为true(非零)。 如果未定义符号,则值为false(零)。 定义的运算符指定如下 -

#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, for example:类型的缩写unsigned char, for example:

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 = @"IoWiki";
   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 : IoWiki
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 Casting

类型转换是一种将变量从一种数据类型转换为另一种数据类型的方法。 例如,如果要将long值存储到简单整数中,则可以将cast long类型设置为int。 您可以使用强制转换cast operator将值从一种类型转换为另一种类型,如下所示 -

(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值。

类型转换可以是隐式的,由编译器自动执行,也可以通过使用转换cast operator显式指定。 每当需要类型转换时,使用强制转换运算符被认为是一种很好的编程习惯。

整数推广

整数提升是将整数类型“小于” intunsigned int的值转换为intunsigned int 。 考虑在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 conversions以将它们的值usual arithmetic conversions为通用类型。 编译器首先执行integer promotion ,如果操作数仍然具有不同的类型,则它们将转换为在以下层次结构中显示最高的类型 -

通常的算术转换

通常的算术转换不是为赋值运算符执行,也不是为逻辑运算符&&和||执行。 让我们以下面的例子来理解这个概念 -

#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被转换为整数但是因为最终值是float,所以通常的算术转换适用,编译器将i和c转换为float并添加它们产生float结果。

Objective-C Log Handling

NSLog方法

为了打印日志,我们使用Objective-C编程语言中的NSLog方法,我们在Hello World示例中使用了该方法。

让我们看一下打印“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 Error Handling

在Objective-C编程中,错误处理由Foundation framework.提供的NSError类提供Foundation framework.

与仅使用错误代码或错误字符串相比,NSError对象封装了更丰富且更具可扩展性的错误信息。 NSError对象的核心属性是错误域(由字符串表示),特定于域的错误代码和包含应用程序特定信息的用户信息字典。

NSError

Objective-C程序使用NSError对象来传达有关用户需要了解的运行时错误的信息。 在大多数情况下,程序会在对话框或工作表中显示此错误信息。 但它也可能会解释信息并要求用户尝试从错误中恢复或尝试自行更正错误

NSError对象包括 -

  • Domain - 错误域可以是预定义的NSError域之一,也可以是描述自定义域和域的任意字符串,不能为nil。

  • Code - 错误的错误代码。

  • User Info - 错误和userInfo的userInfo字典可能为零。

以下示例显示如何创建自定义错误

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

Command-Line Arguments

执行时,可以将一些值从命令行传递给Objective-C程序。 这些值称为command line arguments ,很多时候它们对您的程序很重要,特别是当您想从外部控制程序而不是在代码中对这些值进行硬编码时。

命令行参数使用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 Classes & Objects

Objective-C编程语言的主要目的是为C编程语言添加面向对象,类是Objective-C的核心特性,支持面向对象编程,通常称为用户定义类型。

类用于指定对象的形式,它将数据表示和方法组合在一起,以便将数据操作到一个整齐的包中。 类中的数据和方法称为类的成员。

Objective-C characteristics

  • 该类定义在两个不同的部分,即@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

属性 Properties

在Objective-C中引入了属性,以确保可以在类外部访问类的实例变量。

各部分属性声明如下。
  • 属性以@property开头,它是一个关键字

  • 接下来是访问说明符,它们是非原子的,读写的或只读的,强的,不安全的或不完整的。 这取决于变量的类型。 对于任何指针类型,我们可以使用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 Inheritance

面向对象编程中最重要的概念之一是继承。 继承允许我们根据另一个类定义一个类,这样可以更容易地创建和维护一个应用程序。 这也提供了重用代码功能和快速实现时间的机会。

在创建类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的数据成员和成员函数。 此现有类称为base类,新类称为derived类。

继承的想法实现了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 Polymorphism

polymorphism这个词意味着有许多形式。 通常,当存在类的层次结构并且它们通过继承相关时,会发生多态性。

Objective-C多态意味着对成员函数的调用将导致执行不同的函数,具体取决于调用该函数的对象的类型。

考虑一下这个例子,我们有一个类Shape,它为所有形状提供基本接口。 Square和Rectangle派生自基类Shape。

我们有方法printArea来展示OOP特征polymorphism

#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 Data Encapsulation

所有Objective-C计划都由以下两个基本要素组成 -

  • Program statements (code) - 这是执行操作的程序的一部分,它们被称为方法。

  • Program data - 数据是受程序功能影响的程序信息。

封装是一种面向对象的编程概念,它将操作数据的数据和功能绑定在一起,并保护其免受外部干扰和误用。 数据封装导致了重要的OOP data hiding概念。

Data encapsulation是捆绑数据和使用它们的函数的机制, data abstraction是一种仅暴露接口并从用户隐藏实现细节的机制。

Objective-C通过创建用户定义类型(称为classes支持封装和数据隐藏的属性。 例如 -

@interface Adder : NSObject {
   NSInteger total;
}
- (id)initWithInitialNumber:(NSInteger)initialNumber;
- (void)addNumber:(NSInteger)newNumber;
- (NSInteger)getTotal;
@end

变量total是私有的,我们无法从类外部访问。 这意味着只能由Adder类的其他成员访问它们,而不能由程序的任何其他部分访问。 这是实现封装的一种方式。

接口文件中的方法是可访问的,并且在范围内是公共的。

有一些私有方法,这些方法是在extensions的帮助下编写的,我们将在后面的章节中学习。

数据封装示例

任何使用公共和私有成员变量实现类的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是对外界隐藏的东西,但是课程需要正常运作。

设计策略

除非我们真的需要公开它们,否则我们大多数人都会通过痛苦的经验来学习默认情况下将类成员设为私有。 这只是很好的encapsulation

了解数据封装非常重要,因为它是所有面向对象编程(OOP)语言(包括Objective-C)的核心功能之一。

Objective-C Categories

有时,您可能会发现希望通过添加仅在某些情况下有用的行为来扩展现有类。 为了向现有类添加此类扩展,Objective-C提供了categoriesextensions

如果您需要向现有类添加方法,或许为了添加功能以便在您自己的应用程序中更容易地执行某些操作,最简单的方法是使用类别。

声明类别的语法使用@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 iowiki.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 iowiki.com 2013

即使类别添加的任何方法都可用于类及其子类的所有实例,您仍需要在任何源代码文件中导入类别头文件,否则您将使用其他方法,否则您将遇到编译器警告和错误。

在我们的示例中,由于我们只有一个类,因此我们没有包含任何头文件,在这种情况下我们应该包含如上所述的头文件。

Objective-C Posing

在开始介绍Objective-C中的Posing之前,我想提醒您注意,在Mac OS X 10.5中声明Posing已被弃用,此后无法使用。 因此对于那些不关心这些弃用方法的人可以跳过本章。

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 Extensions

类扩展与类别有一些相似之处,但它只能添加到编译时具有源代码的类中(类与类扩展同时编译)。

类扩展声明的方法是在原始类的实现块中实现的,因此您不能在框架类上声明类扩展,例如Cocoa或Cocoa Touch类,如NSString。

扩展名实际上是没有类别名称的类别。 它通常被称为anonymous categories

声明扩展的语法使用@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 Protocols

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

在上面的例子中,我们已经看到了如何调用和执行delgate方法。 它以startAction开始,一旦进程完成,就会调用委托方法processCompleted以使操作完成。

在任何iOS或Mac应用程序中,如果没有代理,我们将永远不会实现程序。 因此,重要的是我们了解代表的用法。 委托对象应使用unsafe_unretained属性类型以避免内存泄漏。

Objective-C Dynamic Binding

动态绑定确定在运行时而不是在编译时调用的方法。 动态绑定也称为后期绑定。

在Objective-C中,所有方法都在运行时动态解析。 执行的确切代码由方法名称(选择器)和接收对象确定。

动态绑定可实现多态性。 例如,考虑一组对象,包括Rectangle和Square。 每个对象都有自己的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 Composite Objects

我们可以在类集群中创建子类,该类集合定义了一个嵌入在其中的类。 这些类对象是复合对象。

所以你可能想知道什么是类集群。 所以我们将首先看到什么是类集群。

类集群

类集群是Foundation框架广泛使用的设计模式。 类集群在公共抽象超类下组合了许多私有具体子类。 以这种方式对类进行分组简化了面向对象框架的公开可见体系结构,而不会降低其功能丰富性。 类集群基于抽象工厂设计模式。

为了简单起见,我们创建了一个基于输入值处理它的单个类,而不是为类似的函数创建多个类。

例如,在NSNumber中,我们有许多类的集群,如char,int,bool等。 我们将它们全部组合到一个类中,该类负责处理单个类中的类似操作。 NSNumber实际上将这些原始类型的值包装到对象中。

那究竟什么是复合对象?

通过在我们自己设计的对象中嵌入私有集群对象,我们创建了一个复合对象。 此复合对象可以依赖于集群对象的基本功能,仅拦截复合对象希望以某种特定方式处理的消息。 此体系结构减少了我们必须编写的代码量,并允许您利用Foundation Framework提供的测试代码。

这在下图中解释。

Objective-C复合对象

复合对象必须声明自己是集群的抽象超类的子类。 作为子类,它必须覆盖超类的原始方法。 它也可以覆盖派生方法,但这不是必需的,因为派生方法通过原始方法工作。

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 Framework

如果您参考Apple文档,您可以看到Foundation框架的详细信息,如下所示。

Foundation框架定义了Objective-C类的基础层。 除了提供一组有用的原始对象类之外,它还引入了几个定义Objective-C语言未涵盖的功能的范例。 基金会框架的设计考虑了这些目标 -

  • 提供一小组基本实用程序类。

  • 通过为诸如释放之类的事物引入一致的约定,使软件开发更容易。

  • 支持Unicode字符串,对象持久性和对象分发。

  • 提供一定程度的操作系统独立性以增强可移植性。

该框架由NeXTStep开发,后者被Apple收购,这些基础类成为Mac OS X和iOS的一部分。

由于它是由NeXTStep开发的,因此它的类前缀为“NS”。

我们在所有示例程序中都使用了Foundation Framework。 使用Foundation Framework几乎是必须的。

通常,我们使用#import 《Foundation/NSString.h》类的东西来导入Objective-C类,但是为了避免导入太多的类,它们都在#import 《Foundation/Foundation.h》导入。

NSObject是所有对象的基类,包括基础工具包类。 它提供了内存管理的方法。 它还提供了运行时系统的基本接口以及表现为Objective-C对象的能力。 它没有任何基类,是所有类的根。

基于功能的基础类

Sr.No. 循环类型和描述
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加载系统

一组提供对常见Internet协议访问的类和协议。

Objective-C Fast Enumeration

快速枚举是Objective-C的功能,有助于枚举集合。 因此,为了了解快速枚举,我们首先需要了解集合,这将在下一节中进行说明。

Objective-C中的集合

集合是基本结构。 它用于保存和管理其他对象。 集合的整个目的是提供一种有效存储和检索对象的通用方法。

有几种不同类型的集合。 虽然它们都能实现能够容纳其他对象的相同目的,但它们的主要区别在于检索对象的方式。 Objective-C中使用的最常见的集合是 -

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

如果您想了解有关这些结构的更多信息,请参阅Foundation Framework中的数据存储。

快速枚举语法

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 Memory Management

内存管理是任何编程语言中最重要的过程之一。 它是在需要时分配对象的内存并在不再需要时取消分配的过程。

管理对象内存是一个性能问题; 如果应用程序不释放不需要的对象,则其内存占用增加并且性能受损。

Objective-C内存管理技术大致可分为两类。

  • “手动保留 - 释放”或MRR
  • “自动参考计数”或ARC

“手动保留 - 释放”或MRR

在MRR中,我们通过跟踪自己的对象来明确管理内存。 这是使用一个称为引用计数的模型实现的,Foundation类NSObject与运行时环境一起提供。

MRR和ARC之间的唯一区别在于保留和释放由我们手动处理,而后者则自动处理。

下图表示内存管理在Objective-C中的工作方式示例。

Objective-C内存管理

A类对象的内存生命周期如上图所示。 如您所见,保留计数显示在对象下方,当对象的保留计数变为0时,对象将被完全释放,并且其内存将被释放以供其他对象使用。

首先使用NSObject中提供的alloc/init方法创建A类对象。 现在,保留计数变为1。

现在,B类保留了A类的对象,A类对象的保留计数变为2。

然后,C类制作该对象的副本。 现在,它被创建为A类的另一个实例,具有相同的实例变量值。 这里,保留计数是1而不是原始对象的保留计数。 这由图中的虚线表示。

使用release方法由C类释放复制的对象,并且保留计数变为0,因此对象被销毁。

对于初始的A类对象,保留计数为2,必须释放两次才能销毁它。 这是通过A类和B类的释放语句完成的,它们将保留计数分别减少到1和0。 最后,对象被破坏了。

MRR基本规则

  • 我们拥有我们创建的任何对象:我们使用名称以“alloc”,“new”,“copy”或“mutableCopy”开头的方法创建对象

  • 我们可以使用retain获取对象的所有权:通常保证接收到的对象在接收到的方法中保持有效,并且该方法也可以安全地将对象返回给它的调用者。 我们在两种情况下使用retain -

    • 在访问器方法或init方法的实现中,获取我们想要存储为对象属性值的对象的所有权。

    • 防止对象因某些其他操作的副作用而失效。

  • 当我们不再需要它时,我们必须放弃对我们拥有的对象的所有权:我们通过向对象发送释放消息或自动释放消息来放弃对象的所有权。 因此,在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的基本过程仍然是相同的。 它在内部使用保留和释放操作,使开发人员更容易编码而无需担心这些操作,这将减少写入的代码量和内存泄漏的可能性。

还有另一个原则叫做垃圾收集,它在Mac OS-X中与MRR一起使用,但由于它在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
↑回到顶部↑
WIKI教程 @2018