跳至内容

Objective-C 编程

来自 Wikibooks,开放的书籍,开放的世界
(重定向自 Programming:Objective-C 概念)
Objective-C 编程
概念 入门 → 

有一些关键的Objective-C概念与一般面向对象编程实践密切相关。目前,我们不会先看确切的语法。

如果您有面向对象编程的经验,您可能希望跳过这些部分并查看总结部分。

Objective-C 只是 C(C 的超集),带有类似 Lisp 的面向对象语法

[编辑 | 编辑源代码]

Objective-C 中的面向对象与 C++ 有些相似之处,它是一种像 C 样式结构体一样组织内存的方式,不同之处在于,它期望结构体包含指向函数的指针,这些函数可以引用任何定义为实例变量或函数指针的偏移量,因此对象是一个内存位置,从中可以找到实例变量的偏移量,并且有一个指向类定义的指针,该指针提供了类函数的地址。

在 C++ 中,要从类的成员函数调用成员函数,语法是

this -> func ( a, b ); 

(*this).func (a, b);

这表示指向“this”对象的指针,即定义了“this”正在使用的函数的类的实例。

在 Objective-C 中,'self' 是用来替换 'this' 的关键字,调用方式如下:

[self  func: a, b ] 

因此,这里函数的参数不像大多数其他基于 C 的语言那样用括号括起来,而是

在子类的构造函数中,当需要调用父类的构造函数时,Java 中的构造函数是这样调用的。

 Child( a, b, c ) {   super(a); this.b = b; this.c = c;  }

类似地,在 Objective-C 中,对于类接口

 @interface MyClass : MyParentClass {
   MyClassB * b;
   int  c;
 }
 @property (copy, nonatomic) NSInteger  d;
 -(void) func1;
 -(void) func2: (int)param1, (id) param2;

 @end

存在一个省略的 init 构造函数,以及

构造函数可能是

- (id) init: (id)a, (id) b, (int) c , (NSInteger) d {  
   self = [super init:a ];  // -(2)
   if (self) { 
    _b = [[B alloc] initByCopy:b] ;
    _c = c; 
    self.d = d;
   }
   return self;
  }  

并且存在 MyParentClass 的构造函数,它是

- (id) init: (id) a  // - (2a)
{ .. }

上面的例子展示了

  1. id 是类类型的占位符。
  2. 在函数声明或定义中,通常将括号放在返回值类型周围,以及每个参数之前以给出它们的类型。
  3. self 是一个提供的实例变量,它引用类的一个对象,但最初可以是超类型的对象,如 (2) 和 (2a) 中所示。
  4. 约定是调用 alloc,它是类函数,用于让类获取一个对象,然后通过调用一个对象函数来初始化该对象,该函数通常是 'init' 或以 'init..' 开头,所以模式是 my_object = [[A_Class alloc] init:a_param1, a_param2],这意味着“为由 A_Class 模板化的对象分配内存,然后使用 a_param1 和 a_param2 调用对象的 init”。
  5. 一些简单的 C 语言类型,如 int、double、char*,可以用以 NS 开头的更复杂的类来表示。
  6. 在上面的示例中,_b 和 _c 没有在其前面使用 self,但 d 有。d 在类的接口声明部分定义为属性。属性具有省略的用于实例变量的 set/get 函数,并且可以具有属性属性,如 (copy, nonatomic) - copy 表示“self.d = d”是“self.d = [ [NSInteger alloc] initByCopy:d ]”的简写,其中 NSInteger 是 d 的类。
  7. 记住 Objective-C 是 C 的扩展,变量名通常指代一个由 4、8 或 16 个字节组成的短序列,这些字节要么表示数字(int、long、float、double、char),要么表示地址(char*、类实例指针)。这有点不像 Java,在 Java 中,变量是对对象的引用,对象的引用方式以及它们的内存管理方式是省略或隐藏的。Objective-C 框架后来在 NS.. 类家族中添加了引用计数自动释放内存管理。

C 和标准 Unix 系统接口是 Objective C 的基础

[编辑 | 编辑源代码]

Objective C 中可以使用 C 中可用的任何标准 Unix 库,因此包括对文件输入/输出、动态 (malloc/free) 分配、字符串函数、数学函数、进程创建、同步以及通过文件锁、线程锁、线程条件变量、信号量、管道、套接字、共享内存管理、信号进行进程间通信等服务的标准系统接口。但是,Objective C 允许将数据结构与方法分组,并使用面向对象的模式(如多态和委托(通过 Objective-C 的“协议”功能))来组织结构化编程的快捷方式。其他功能还包括自动引用计数,以便动态分配的对象在没有垃圾收集的情况下自动释放。这导致了标准操作系统接口函数 (malloc/free) 被标准 Objective C. 面向对象的习惯用法所取代,例如来自 NSObject 的习惯用法,例如

NS<some class>* p = [[NS<some class> alloc] init] ; 

对于熟悉点语法(例如 Java)的人来说,这意味着与

MYClass p = (MyClass.alloc()).init();

“调用静态工厂类方法 alloc(),并在对象上调用 init()”。

在 C 中,只有

 MyStruct * p = malloc ( size of(MyStruct));

稍后,指针 p 可能会与另一个指针(比如 q)共享内存地址,并且会有 2 个引用,然后如果稍后 q = z,甚至更晚 p = z,但是等等,存在内存泄漏,因此必须在 p 上调用 free(),因为它是对上面 malloc 调用生成的内存分配的最后一个引用。

 free(p);

在旧的 Objective-C 框架中,

 [p dealloc];

然而,自动引用计数或 ARC,意味着当 p = z 时,在上面的示例中,p 指向的 NS 对象的引用计数降至零,并且会在 ARC 支持框架持有的隐藏指针上调用 free()。

示例 2,而不是

char * charbuf[maxlength]; 
snprintf ( charbuf, maxlength-1, "hello %s , %d times!", "world", 2);

NSString* mystrbuf = [[NSString alloc] initWithFormat: "hello %s , %d times!", "world", 2];

什么是面向对象编程?

[编辑 | 编辑源代码]

面向对象编程(通常简称为 OO 或 OOP)是一种编程范式,其中代码以称为类的单元编写,其中与数据结构相关的函数在其中声明和定义。例如,ANSI C 在结构体s 中,类似地,OO 语言将数据存储在类或对象中,但还声明了对结构化数据(对象)的实例进行操作的函数。类本身可以是一个对象,具有相关数据和类函数,但每个进程空间只有一个实例。(一般来说,操作系统为每个正在运行的程序分配一个进程,但允许多进程程序,这些程序是启动其他程序的程序)。

例如,如果您正在创建文字处理器,您可以通过一个对象来表示文档,该对象以文本作为其数据,并与它关联不同的任务,以对文档的数据执行操作,例如“拼写检查”。

然后,文字处理器将是一个可能绘制窗口并显示文档文本的小程序。拼写检查按钮将在该文档的对象上执行“拼写检查”任务。

但是,如果我们同时编辑两个文档,它们在文档内容(数据)方面可能会有所不同。但所有文档或多或少都是一样的:每个文档都包含一个用于其文本的空间,并且具有几个定义在文本上执行操作的任务。每个文档对象本质上具有相同的形式。我们将此形式称为对象的,我们经常为它命名。让我们将文档类命名为文档(样式说明:我们通常将类的名称大写)。

现在,每个文档对象都具有文档的形式。我们说每个文档对象都是文档的一个实例。因此,例如,我们的文字处理器在文档类的实例上执行操作。有时我们可能会将文档类的实例简称为“一个 文档".

请记住,每个对象都包含一些数据。例如,对于文档类来说,每个文档都有数据来保存文档的文本。我们把每个对象持有的数据称为实例变量。因此,一个文档可以指定一个实例变量来保存文本,或一个实例变量来保存字数、作者或我们可能想要指定的任何其他数据。每个文档都有自己的实例变量,与其他任何文档的实例变量是分开的。

每个对象还可以执行一些针对其实例变量的任务。在 Objective-C 中,我们称这些任务为方法。对象的方 法在其对象的实例变量上操作。

然而,对象不仅仅如此。使用 Objective-C,我们可以用对象做很多有趣的事情。

使用对象

[edit | edit source]

让我们继续以我们的文字处理程序为例。假设我们想要创建一个新的文档实例,以便我们可以使用它。这个过程称为实例化

现在我们已经拥有了一个活动的对象实例,我们可以通过使用对象的方法来使用它。我们将在稍后学习定义和使用方法。每个方法都像 C 中的函数一样,在 Objective-C 中,语法只稍有变化。

当我们完全使用完对象并想要摆脱它(以便它不再占用内存)时,我们会执行所谓的对象销毁释放。这就像free一个malloc的数组,例如在 C 中。但是,如果在使用对象方法的过程中,我们为其实例变量分配了一些额外的内存,我们必须释放这部分内存。

对象技术

[edit | edit source]

当我们创建新类时,我们可能希望基于之前创建的类来创建新对象的的行为。例如,如果我们想要创建一个包含样式信息的文档类,我们可能希望基于之前创建的`Document`类来创建这个类。

这是一个非常常见的特性,它被包含在面向对象系统中,被称为继承。当一个类从另一个类继承时,新类将采用旧类的形式,并且可以添加额外的实例变量和方法。

因此,如果我们想要创建一个`StyledDocument`类,我们将会从`Document`类继承。在 Objective-C 中,一个类只能从另一个类继承。这被称为单继承

接口和实现

[edit | edit source]

就像在现实世界中一样,对象的使用方式和对象工作方式之间存在分离。例如,使用我们的文字处理程序,拼写检查器有一个一致的接口(如何使用它),例如跳转到下一个拼写错误的单词、后退和更正的按钮。但在幕后,拼写检查器的工作方式(实现)可以用很多不同的方式完成。

在 Objective-C 中,接口实现是分开的。这使得不同的对象能够以一种明确定义的方式协同工作和链接。例如,为了改变电视的频道,你会使用电视机上按钮的接口,而不是打开它并在电子元件上乱动。

将实现与接口分开意味着我们可以更改实现中的任何内容(甚至用其他实现替换它),而所有内容都应该仍然可以工作,因为对象(按钮等)之间只通过它们的接口进行交互。

总结

[edit | edit source]

因此,在学习 Objective-C 语法之前,让我们回顾一下

一个对象是一个实例
它定义了被称为方法的任务
以及被称为实例变量的数据
一个可以从另一个单继承)继承(并且只能从一个类继承)
华夏公益教科书