结尾草草地写了这么个Demo:定义一个Student类,实例化出一个xiaoming的对象,并同时使用python和java与Objective-C实现它们。这段代码只是演示了一些最基本的OC语法,但也留下了几个令初学者费解的问题:
NSObject
是什么鬼?并没有写构造方法,
OC
里真的没有构造方法吗?self
是什么鬼?OC
里已经演示了如何继承和封装,但如何实现多态呢?
带着这些问题,我们继续探讨。
Objective-C中的类和 NSObject
C语言中有类似对象的结构体,它可以保存一些相关的数据,在 OC
里则实打实地实现了类的概念。
向类发送 alloc
消息后就可以为其分配一个对象的内存空间(从语法层面上看, Java
和 python
是没有这一步的)。然而此时对象并没有被“构造”出来,你还需要为其发送 init
消息。 init
是什么呢?答:是 NSObject
对象的构造方法。
初识 NSObject
那么 NSObject
是什么呢?答:它是 OC
中大多数类的父类。尽管不像单根的 java
那样是所有类的父类,但我们暂时可以不用在意这些细节。另外要说明一点的就是, OC
是单继承的,即一个子类不可能同时继承自多个父类。
诸如 alloc
和 init
这些方法都是 NSObject
自带的,因此,理论上说,所有继承自 NSObject
的子类都会自带这些方法。
于是我们回过头来再看 xiaoming
的诞生过程,这里面就大有文章了:
Student *xiaoming = [[Student alloc] init];
/*
1. 对Student类发送allc消息,告诉它分配内存;
2. 调用初始化方法init,完成构造。
3. 将完成构造的Student对象的内存地址赋给指针变量xiaoming
*/
这里的 init
,实际上就是发送给 NSObject
发送的消息(或者说是调用了 NSObject
的构造方法,发送消息这种“术语”,说起来真别扭~)。我们完全可以为自己的类指定自己的初始化方法,但在此之前,先了解一些别的东西。
有一种指针叫 id
有一种空虚叫 nil
id
是一个指向任何一个继承了Object(或者NSObject)类的对象,因为可以用来做泛型,有时候也叫泛型指针,但这里所谓泛型的实现方式和 java
有比较大的差别。
需要注意, id
本身就表示指针,所以定义id类型的变量时请务必不要加 *
,否则会报错。
nil
和 C
或 java
中的 NULL
相同,代表空对象, nil
并不会存在于内存中,所以当 *xiaoming=nil
时,指针 xiaoming
将不会指向内存中的任何一个区域。这也是释放内存的方法之一——指向原对象的指针指向 nil
后,堆内存的那一坨对象就少了一处被引用,如果任何指针都没有引用那坨对象,OC会自动释放那坨对象所占的内存。 OC
的内存管理我也搞不清,以后再探讨。
还有一种比较少用的首字母大写的 Nil
,它和 nil
的区别仅在于前者用来表示指向一个“不存在”的类。nil和Nil在使用上是没有严格限定的,它们俩可以相互替代。
再补充一个东西吧: NSNull
和 nil
一样,也表示空,但前者拥有一个有效的内存地址,并且,这货是继承自 NSObject
的。实际应用中,它常在可变数组这类东东里出现,我们暂时不去深入,知道就好。
了解 self
、 super
和 isa
self
和 java
中的 this
或 python
中的 self
类似,它是一个指向类或实例本身的指针,id类型。它是类或对象的隐藏参数。
super
和 java
或 python
中的 super
一样,表示指向父类,书上说,它是个编译器指示符,不明觉厉,然而这并不影响我们使用。
isa
是对象指向自己的类的指针,也是 id
类型,每一个继承自 NSObject
的类的实例都有它。
以上只是对 NSObject
常用的一些概念的简介,并不全。
我们现在可以写一个类的构造方法了,上代码:
#import <Foundation/Foundation.h>
//------ interface ------
@interface Student: NSObject
// 我们用 property 替代 setter/getter
@property NSString *name;
@property int score;
// 声明构造方法1
-(id)initWithName:(NSString *) newName;
// 声明构造方法2
-(instancetype)initWithName:(NSString *) newName score:(int) newScore;
@end
//------ implementation ------
@implementation Student
@synthesize name = _name; // 让编译器帮你生成getter/setter
@synthesize score = _score;
/* 构造方法1 start */
-(id)initWithName:(NSString *) newName{
/* 先调用父类指定的初始化方法 */
self = [super init];
/* 父类指定的初始化方法是否成功创建了父类对象? */
if(self){
// 初始化一些值
_name = newName;
}
// 返回初始化对象的新地址
return self;
}
/* 构造方法1 end */
/* 构造方法2 start */
-(instancetype)initWithName:(NSString *) newName score:(int) newScore{
/* 初始化方法的返回值类型也可以是instancetype */
/* 这里可以调用另一个指定的初始化方法 */
self = [this initWithName:newName];
/* 父类指定的初始化方法是否成功创建了父类对象? */
if(self){
// 初始化一些值
_score = newScore;
}
// 返回初始化对象的新地址
return self;
}
/* 构造方法2 end */
@end
希望通过上述代码,可以了解 OC
的类是如何实例化对象的,以及了解如何自定义构造方法。这里要说明一点,按照 OC
的编码习惯,构造方法名,或者叫初始化方法名,都是以 init
开头的。
我们再举一个栗子,让我们的对象能够返回自己的类:
+ class
{
return self; // 类方法返回自己
}
- class
{
return (id)isa; // 实例通过isa返回类
}
权限修饰
我们知道 java
里是有权限修饰的: protected
、 public
、 private
,这些权限在 OC 里也存在,但方法略有不同。
对于属性来说,OC提供了一些关键字修饰属性,默认是protected: @protected
、 @public
、 @private
。另外还有 @package
权限——变量在框架内为 protected,对于框架外则是私有的。
OC里,没有“包”的概念。
// ...
{
@public
int i;
@private
NSString *name;
}
// ...
对于方法来说, OC
并没有提供权限修饰符,但可以通过声明的位置来决定方法是否是私有:如果方法声明在 *.h
文件中,就是公有,如果在 *.m
文件,则是私有。
关于 NSObject
的介绍,这里还不是很全面,接下来该说说 OC
中的一些常用类型了。
未完待续。