既然是面向对象语言,类和对象显然是应该优先考虑的内容。鉴于本系列已经假定你已经熟悉 C++ 语言,自然就不会去解释类和对象的含义。我们直接从 Objecti-C 和 C++ 的区别开始说起。
Objetive-C 使用的是严格的对象模型,相比之下,C++ 的对象模型则更为松散。例如,在 Objective-C 中,所有的类都是对象,并且可以被动态管理:也就是说,你可以在运行时增加新的类,根据类的名字实例化一个类,以及调用类的方法。这比 C++ 的 RTTI 更加强大,而后者只不过是为一个“static”的语言增加的一点点功能而已。C++ 的 RTTI 在很多情况下是不被推荐使用的,因为它过于依赖编译器的实现,牺牲了跨平台的能力。
根类,id
类型,nil
和Nil
的值
任何一个面向对象的语言都要管理很多类。同 Java 类似,Objective-C 有一个根类,所有的类都应该继承自这个根类(值得注意的是,在 Java 中,你声明一个类而不去显式指定它继承的父类,那么这个类就是Object
类的直接子类;然而,在 Objective-C 中,单根类的子类必须被显式地说明);而 C++ 并没有这么一个类。Cocoa 中,这个根类就是NSObject
,它提供了很多运行时所必须的能力,例如内存分配等等。另外需要说明一点,单根类并不是 Objective-C 语言规范要求的,它只不过是根据面向对象理论实现的。因此,所有 Java 虚拟机的实现,这个单根类都是Object
,但是在 Objective-C 中,这就是与类库相关的了:在 Cocoa 中,这个单根类是NSObject
,而在 gcc 的实现里则是Object
。
严格说来,每一个类都应该是NSObject
的子类(相比之下,Java 应该说,每一个类都必须是Object
的子类),因此使用NSObject *
类型应该可以指到所有类对象的指针。但是,实际上我们使用的是id
类型。这个类型更加简短,更重要的是,id
类型是动态类型检查的,相比来说,NSObject *
则是静态类型检查。Objective-C 里面没有泛型,那么,我们就可以使用id
很方便的实现类似泛型的机制了。在 Objective-C 里面,指向空的指针应该声明为nil
,不能是NULL
。这两者虽然很相似但并不可以互换。一个普通的 C 指针可以指向NULL
,但是 Objective-C 的类指针必须指向nil
。正如前文所说,Objective-C 里面,类也是对象(元类 Meta-Class 的对象)。nil
所对应的类就是Nil
。
类声明
属性和方法
在 Objective-C 里面,属性 attributes 被称为实例数据 instance data,成员函数 member functions 被称为方法 methods。如果没有特殊说明,在后续文章中,这两组术语都会被混用,大家见谅。
C++
class Foo { double x; public: int f(int x); float g(int x, int y); }; int Foo::f(int x) {...} float Foo::g(int x, int y) {...}
Objective-C
@interface Foo : NSObject { double x; } -(int) f:(int)x; -(float) g:(int)x :(int)y; @end @implementation Foo -(int) f:(int)x {...} -(float) g:(int)x :(int)y {...} @end
在 C++ 中,属性和成员函数都在类的花括号块中被声明。方法的实现类似于 C 语言,只不过需要有作用于指示符(Foo::
)来说明这个函数属于哪个类。
Objective-C 中,属性和方法必须分开声明。属性在花括号中声明,方法要跟在下面。它们的实现要在@implementation
块中。
这是与 C++ 的主要不同。在 Objective-C 中,有些方法可以不被暴露在接口中,例如 private 的。而 C++ 中,即便是 private 函数,也能够在头文件中被看到。简单来说,这种分开式的声明可以避免 private 函数污染头文件。
实例方法以减号 - 开头,而 static 方法以 + 开头。注意,这并不是 UML 中的 private 和 public 的区别!参数的类型要在小括号中,参数之间使用冒号 : 分隔。
Objective-C 中,类声明的末尾不需要使用分号 ;。同时注意,Objective-C 的类声明关键字是@interface
,而不是@class
。@class
关键字只用于前向声明。最后,如果类里面没有任何数据,那么花括号可以被省略。
前向声明
为避免循环引用,C 语言有一个前向声明的机制,即仅仅告诉存在性,而不理会具体实现。C++ 使用 class 关键字实现前向声明。在 Objective-C 中则是使用@class
关键字;另外,还可以使用@protocol
关键字来声明一个协议(我们会在后面说到这个概念,类似于 Java 里面的 interface)。
C++
//In file Foo.h #ifndef __FOO_H__ #define __FOO_H__ class Bar; //forward declaration class Foo { Bar* bar; public: void useBar(void); }; #endif //In file Foo.cpp #include "Foo.h" #include "Bar.h" void Foo::useBar(void) { ... }
Objective-C
//In file Foo.h @class Bar; //forward declaration @interface Foo : NSObject { Bar* bar; } -(void) useBar; @end //In file Foo.m #import "Foo.h" #import "Bar.h" @implementation Foo -(void) useBar { ... } @end
private,protected 和 public
访问可见性是面向对象语言的一个很重要的概念。它规定了在源代码级别哪些是可见的。可见性保证了类的封装性。
C++
class Foo { public: int x; int apple(); protected: int y; int pear(); private: int z; int banana(); };
Objective-C
@interface Foo : NSObject { @public int x; @protected int y; @private int z; } -(int) apple; -(int) pear; -(int) banana; @end
在 C++ 中,属性和方法可以是private
,protected
和public
的。默认是private
。
在 Objective-C 中,只有成员数据可以是private
,protected
和public
的,默认是protected
。方法只能是public
的。然而,我们可以在@implementation
块中实现一些方法,而不在@interface
中声明;或者是使用分类机制(class categories)。这样做虽然不能阻止方法被调用,但是减少了暴露。不经过声明实现一些方法是 Objective-C 的一种特殊属性,有着特殊的目的。我们会在后面进行说明。
Objective-C 中的继承只能是public
的,不可以是private
和protected
继承。这一点,Objective-C 更像 Java 而不是 C++。
static 属性
Objective-C 中不允许声明 static 属性。但是,我们有一些变通的方法:在实现文件中使用全局变量(也可以添加static
关键字来控制可见性,类似 C 语言)。这样,类就可以通过方法访问到,而这样的全局变量的初始化可以在类的 initialize 方法中完成。
5 评论
“每一个类都应该是 NSObject 的子类”这一句我不解,不是说NSObject是Cocoa的类吗?这个语言强制要求类必须有个根类却没有定义一个universial的根类?那么NSObject的根类又是什么呢?
重新查阅一下资料:为了使用和管理的方便,Cocoa 给我们定义了一个 NSObject,可以作为 Root Class 使用。但是,即使在 Cocoa 中,还有一个类不是继承 NSObject 的,就是 NSProxy。由这一点可以看出,语言没有要求必须是单根的。但是,Apple 推荐用户定义的类继承 NSObject,因为可以利用继承自 NSObject 的一系列函数与 Object-C Runtime 进行交互,从而获得内存分配、管理等的能力。除非你重写了 NSObject 里面实现的那些和 Object-C Runtime 交互的消息,比如 alloc 之类的,所以如果不是十分特殊的要求,从 NSObject 及其子类继承是正确的选择。详情见:http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSObject.html
我赞同你的说法,这个NSObject提供了很多强大的功能,对于内存管理等很多事情提供了方便的支持。只是觉得那句话的措辞有点过了,尤其那句“严格来说”,呵呵。不过这不是什么大问题。你文章译的很仔细,排版也很用心,赞一个。
经查阅,代码有误:
@interface Foo : NSObject
{
@public:
int x;
@protected:
int y;
@private:
int z;
}
正确是:
@interface Foo : NSObject
{
@public
int x;
@protected
int y;
@private
int z;
}
谢谢指出!已经修改过来。