- 类与对象
- 数据成员
- 函数成员
TODO:
- 初始化器和析构器
- 继承
- 多态
类与对象
Objective-C(以下简称OC) 是一种面向对象语言,因此除了基本数据类型,它具有类类型。OC 中类的声明形式类似:
@interface MyClass: NSObject
{
NSString* private; //实例变量
@public NSString* public; //实例变量
}
@property int num; //属性
-(void)ObjectMethod: (int)num; //对象方法,又叫实例方法
+(void)ClassMethod; //类方法
-(NSString*)private ;
-(void)setPrivate: (NSString*)str;
@end
OC 用 @interface
声明类,相当于C++语法中的class
关键字。@end
表示声明的结束。OC中的大部分中大部分类都会直接或者间接继承自NSObject
,这是因为NSObject
中定义了一些OC对象通用的方法,比如定义一个类时[[MyClass alloc] init]
,其中alloc
和init
方法就是在NSObject
中定义的,否则我们就要在类定义中自己实现这两个方法。
OC中类的声明放在.h
文件,类是现实存在.m
文件。上面声明的类其实现类似:
@implement MyClass
-(void)ObjectMethod: (int)num
{
self.num = num;
}
+(void)ClassMethod
{
NSLog(@"This ClassMethod");
}
-(NSString*)private {
return private;
}
-(void)setPrivate: (NSString*)str {
private = str
}
定义一个类对象的语法如下:
MyClass* myClass = [[MyClass alloc] init]
大概是因为OC中的类对象都是分配在堆上的,所以,类对象都声明为指针的形式。
数据成员:属性和实例变量
上述类声明中的有一个数据成员,就是
@property in num;
属性的存取方式
myClass.num = 5;
int someNum = myClass.num;
对于任何属性,编译器都会自动生成一个与之关联的实例变量。比如与num
关联的实例变量是_num
。同时还生成一个getter访问器方法和一个setter访问器方法。
-(int)num {
return _num;
}
-(void)setNum: (int)num {
_num = num;
}
可以发现这里的_num
和它的两个访问器和我们在MyClass
实现中定义的NSString* private;
形式是类似的。
private
和public
一样是实例变量,不同的是,一般的实例变量(如:private
)只能在类的内部访问,都是私有的。为了能在类的外部访问(如myClass->public = @"hello";
),需要在实例变量声明语句加上@public
前缀(如:public
)。
虽然private
不能在外部通过->
来访问,但因为我们为private
编写了getter访问器和setter访问器,在OC中,我们就可以使用myClass.private = @"world"
这样的语法来访问private,其实编译器会自动将该语句转化成[myClass setPrivate:@"world"]
,也就是实际是调用setter方法来访问private
实例变量的。
可想而知,如果没有属性,那么对于每个我们希望通过myClass.XXX
形式来访问的实例变量,我们都需要为其编写两个访问器方法,这实在是太过繁琐。有了属性这个语法糖,就可以让编译器自动为我们完成这些工作了。
同样,有了属性,我们也不需要再使用@public
关键字,通过->
指针访问符来访问实例变量了。
需要注意的时,对于在.h
和.m
文件中分别编写类声明和类实现代码情况,即我们通常所遵循的方式。外部使用时通过#import "xxx.h"
只能知道xxx.h
文件中的定义,如果我们将某个实例变量定义在xxx.m
的实现代码中,那么就是为该实例变量加上@public
前缀,外部对象也不能访问该实例变量,因为它根本不知道该实例变量的存在。这点对于方法也是成立的,尽管方法都是public的,但如果方法只在.m
文件中定义,没有在.h
文件中声明,那么对于外部对象该方法就相当于是私有的。相当于私有并不等于私有,这点在编译器报错时可以体现。只定义在.m
文件中情况,访问时编译器会提示没有定义该实例变量或者方法,而如果是声明在.h
文件中非@public
实例变量,访问时编译器会提示该实例变量是受保护的。
函数成员:方法
OC中的方法都是public的,没有 private 和 protected 方法。
OC中类的方法分为实例方法
和类方法
,声明形式如下
-(void)ObjectMethod: (int)num; //对象方法,又叫实例方法
+(void)ClassMethod; //类方法
在语法层面,实例方法就是通过实例对象来调用的方法,如[myClass ObjectMethod:10]
,定义时在方法名前加-
前缀;类方法就是通过类名来调用的方法,如[MyClass ClassMethod]
,定义时在方法名前加+
前缀。
[[MyClass alloc] init]
,其中alloc
就是类方法,init
就是实例方法。
类方法中是不能访问本类的实例成员的。在逻辑上类方法可以在类对象不存在时调用,此时实例变量都还不存在,那么通过类方法访问实例成员必然出错。即使类对象存在,假设有多个类对象,那么类方法是访问那个类对象的实例成员也是无法确定。在实现上,编译器会为实例方法自动添加指向当前对象的self
指针参数,通过self
就能确定所访问的对象。而类方法是没有self
参数的,所以类方法无法确定是哪个类对象。
OC中方法的外部参数名不同,就是不同的方法。因此可以写方法名相同,参数类型和数量相同,以及返回值相同的,只有外部参数名不同的多个方法。
-(void)sum: (int)arg1 arg2: (int)arg2;
-(void)sum:(int)arg1 secondArg: (int)arg2;
这样的两个方法是可以同时存在的。
初始化器和析构器
初始化器
MyClass* myClass = [[MyClass alloc] init]
OC中创建一个对象需要配合使用alloc
和init
两个方法。正如方法名的字面意思,alloc
方法用于分配对象空间,init
方法用于对对象的实例变量进行初始化操作。
alloc
方法在NSObject
中已经定义,这也是我们的类要继承于NSObject
的原因之一。alloc
方法会将分配的内存空间用0来填充,这样所有的实例变量的值就会是0或者nil。
在init
的方法中,会先调用父类的init
方法,再初始化自己的实例变量。
init
是对象初始化器,在调用init
之前还会调用一个类初始化器initialize
。
@implementation MyClass
...
-(id)init {
self = [super init];
NSLog(@"this init");
return self;
}
+(void)initialize {
if self == [MyClass class] {
NSLog(@"this is initialize");
}
}
...
@end
在类定义中实现这两个初始化器,然后调用MyClass* myClass = [[MyClass alloc] init]
会输出如下结果:
类型初始化器initialize
只能有一个,而对象初始化器init
可以有多个。
- (id)init;
- (id)initWithName: (NSString*)name;
- (id)initWithLocation: (NSPoint*)location;
在初始化器中,要使用实例变量,不要使用属性。
析构器 dealloc
在析构器中,主要做三件事
- ARC对对象属性的引用计数器减1,这个造作是自动完成。
- 手动释放我们自己分配的动态内存
- 关闭非内存资源,比如文件句柄,socket连接等