跳转到内容

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 允许将数据结构与方法分组,并为 GUI 构建等领域提供面向对象模式(如多态性和委托(通过 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 在structs 中有数据,类似地,面向对象语言将数据存储在类或对象中,但也声明对结构化数据(对象)的实例进行操作的函数。类本身可以是一个对象,具有相关的数据和类函数,但每个进程空间只有一个实例。(通常,操作系统为每个正在运行的程序提供一个进程,但允许多进程程序,这些程序是启动其他程序的程序)。

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

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

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

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

记住,每个对象都包含一些数据。例如,对于Document类,每个Document都包含用于保存文档文本的数据。我们将每个对象保存的数据称为实例变量。因此,一个Document可以指定一个实例变量来保存文本,或者指定一个实例变量来保存字数统计、作者或我们可能要指定的任何其他数据。每个Document都拥有与任何其他可能存在的Document的实例变量分开的实例变量。

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

但是,对象不仅仅是这些。在 Objective-C 中,我们可以使用对象做很多有趣的事情。

使用对象

[编辑 | 编辑源代码]

让我们继续使用文字处理器的例子。假设我们要创建一个Document的新实例,以便我们可以对其进行操作。这个过程称为实例化

现在我们有了对象的一个活动实例,我们可以通过使用对象的方法来对其进行操作。我们将在后面学习定义和使用方法。每个方法只是像 C 中的函数一样工作,在 Objective-C 中,只是语法略有变化。

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

对象技术

[编辑 | 编辑源代码]

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

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

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

接口和实现

[编辑 | 编辑源代码]

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

在 Objective-C 中,接口实现是分开的。这允许不同的对象以一种定义明确的方式协同工作和链接。例如,要切换电视频道,您使用电视机上按钮的接口,而不是打开电视机并摆弄电子设备。

将实现与接口分开意味着我们可以更改实现中的任何内容(甚至用另一个实现替换它),并且一切仍然可以正常工作,因为对象(按钮等)仅通过它们的接口相互交互。

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

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