Objective-C基础语法 类

一、类声明

Objective-C并没有像其他面向对象语言那样提供声明类的专用关键字或者语法,而是用编译处理指令来实现,特征是类声明语句都须以@符号开始

类声明的编译处理指令以 @interface 开始,以 @end 结尾,在这之间代码便是类属性和方法的声明。

1
2
3
4
5
@interface 类名 : 父类名
属性声明
实例方法声明
类方法声明
@end
属性声明

属性是获取和(或)设置类实例状态的接口。

语法如下:

1
@property (可选特性) 属性类型 属性名称

//TODO 属性的特性可以设置属性的存储主义和其他行为。

方法声明

语法如下:

1
方法类型 (返回值类型) 方法名部分1:(参数类型1)参数名1 方法名部分2:(参数类型2)参数名2 ...;

例如:

1
- (void) setpointX:(int)x andY:(int)y;
  • 方法类型 -表示该方法为实例方法 +表示该方法为类方法

  • 返回值类型和参数类型放入()中

  • C函数返回值缺省为int型。OC中缺省参数为id型的对象类型。

  • 习惯上类名首字母大写,方法名全部小写​。

二、类实现

类的实现要在@implementation这个编译处理指令中进行

1
2
3
4
5
6
7
@implementation 类名
{
变量声明
}
属性定义
实例方法定义
@end
变量声明

类的实例变量可以在类的接口或实现部分中声明。不过在类的公有接口中声明变量违反了OOP的特性之一—封装。所以最好在类的实现部分声明实例变量。类可以没有实例变量,这时{}可以忽略。

Object-C提供多种编译器指令设置变量的作用范围(即变量的访问控制):

  • private: 只能在声明它的类中被访问

  • protected: 只能在声明它的类以及其子类中被访问

  • public: 可以被任意代码访问(这会违反类的封装原则)

  • package: 可以被同一个包内的其它类实例或函数访问。这种作用范围通常用于库或框架类。

当用变量保存对象的时候,应该始终使用指针类型。Objective-C对变量包含的对象支持强弱两种类型。强类型指针的变量类型声明包含了类名。弱类型指针使用id作为对象的类型。弱类型指针常用于类的集合,在集合中对象精确的类型可以是未知的。

属性定义

在大多数情况下,属性是由变量支持的,所以,属性定义中会含有属性的getter,setter方法的定义、变量的声明,并在getter/setter方法中使用这个变量。OC提供了多种定义属性的方式:显式定义、关键字补全、自动补全。

  1. 显式定义: 在相应的代码中明确定义属性的访问器(getter,setter)方法。

  2. 关键字补全: 通过使用@synthesize关键字,可以使编译器自动生成属性定义。属性代码会在相应的类实现部分自动补全。

  3. 自动补全: Clang/LLVM(4.2+)是苹果公司推荐使用的OC编译器,他支持对已声明的属性进行自动补全。这意味着编译器可以自动补全以下已声明的属性:没有使用关键字(如@synthesize)进行代码补全的属性、不是通过@dynamic指令自动生成的属性。 编译器会自动补全已声明的方法和相应的变量。

方法定义

方法定义与方法声明一致,只是不由分号结束,而是使用{}来实现逻辑。


下面是一个类的示例代码:

类接口代码(h文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import <Foundation/Foundation.h>
@interface Dog : NSObject //声明类

//声明属性
@property (readonly) NSString* color;
@property NSInteger age;
@property NSString* barksType;
@property BOOL canWaveTail;

//声明方法
- (void) bark;
- (void) barkWithType:(NSString*)type Times:(NSInteger)tms;

//显式声明属性访问器方法
//如果属性是writeable的,则getter和setter需要同时显式声明
-(BOOL) canWaveTail;//getter访问器,名称和属性的名称相同
- (void) setCanWaveTail:(BOOL)canWaveTail;//以set开头,后跟首字段大写的属性名称

@end /* interface Dog */

类实现代码(m文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#import <Foundation/Foundation.h>
#import "Dog.h"
//class Dog
@implementation Dog
{
//声明变量
@private
NSInteger dogAge;
NSInteger defaultBarkTimes;
BOOL _canWaveTail;
}
@synthesize age = godAge;//通过关键字补全指定属性使用的变量

-(id)init
{
if(self = [super init])
{
defaultBarkTimes = 2;
_barksType = @"Wang"; //给barkType的变量赋值。此处的_barksType是由编译器自动生成的变量。其与属性的名称相同,但是添加了下划线作前缀
self.age = 3; //可以直接给属性age赋值。
_color = @"black";
}
return self;
}

//显式定义属性访问器方法
//如果属性是writeable的,则getter和setter需要同时显示定义
- (BOOL) canWaveTail
{
return _canWaveTail; //此处的_canWaveTail无法由编译器生成,需要指定变量
}

- (void) setCanWaveTail:(BOOL)canwavetail
{
_canWaveTail = canwavetail;
}

//定义方法
- (void) bark
{
NSString* str = @"";
for(NSInteger i = 0; i<defaultBarkTimes; i++)
{
str = [str stringByAppendingString:self.barksType];
str = [str stringByAppendingString:@" "];
}
NSLog(@"%@",str);
}

-(void) barkWithType:(NSString *)type Times:(NSInteger)tms
{
NSString* str = @"";
for(NSInteger i = 0; i<tms; i++)
{
str = [str stringByAppendingString:type];
str = [str stringByAppendingString:@" "];
}
NSLog(@"%@",str);
}

@end

三、类实例化

构造和初始化

在Objective-C中,对象内存分配和对象初始化是在两个不同的方法中完成的:

  • 内存分配由类方法alloc完成,这将初始化所有的实例数据为0,除了一个名为isa的NSObject的指针。这个指针将在运行时指向对象的实际类型。alloc既没有将对象的实例变量初始为适当的值,也没有为这个对象准备其它必需的资源。

  • 初始化将由实例方法instance method完成,为个方法通常命名为init。

类的实例化将分为明确的两步:内存分配(alloc)和初始化(init)。alloc消息被发送给类,分配对象内存,init消息则发送给新分配的对象,随后其父类的init被调用,一直到NSObject的init方法被调用。

alloc和init都将返回类的实例,init返回的实例才是完整的实例。

一般来说实例化的代码为:

1
ClassName* obj = [[ClassName alloc] init];

C++中检查内存分配是否成功判断返回值是否为NULL,OC中判断返回值是否为nil。

  • 在根类NSObject中,定义了实例化根类以及继承自根类的类的类方法alloc。

  • 和C++不同,初始化方法与普通方法没有差别,方法名不必与类名完一致,通常使用的方法名为 init。NSObject实现了初始化方法init。

  • NSObjet的alloc方法和init方法的返回值类型为id。id型是代表对象的广义类型,用以存储对任何OC对象的引用,而且不区分对象所属的类。

四、方法的调用和属性的访问

访问属性

Objective-C有两种访问属性的机制:访问器和点语法。

使用访问器方法的语法如下:

1
2
[object getter];
[object setter:value];

使用点语法的语法如下:

1
2
object.propertyName;
object.propertyName = Value
调用方法

在Objective-C中,对象通过方法传递消息。下面是调用实例方法的语法:

1
[object funName1:value1 funName2:value2];

下面是实例化对象、访问属性和调用方法的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>
#import "Dog.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog* dog = [[Dog alloc]init]; //实例化对象
[dog bark]; //调用方法
[dog barkWithType:@"Wang aowu~" Times:3];
dog.canWaveTail = YES; //点语法访问属性
[dog setAge:3]; //访问器访问属性
NSLog(@"dog`s Age : %ld",dog.age);
}
return 0;
}

五、协议

类是对象的抽象,对协议则是对类的抽象。协议使Objective-C支持多重继承的概念。

协议的声明以@protocol指令开始,后跟协议名称,以@end指令结束。协议拥有必选方法和可选方法,可选方法不是协议必须实现的方法。必选方法和可选方法分别以@required和@optional指令标记,如果没有标记,则默认为是必选方法。

1
2
3
4
5
6
7
@protocol 协议名称
属性声明
@required
方法声明
@optional
方法声明
@end

类接口或协议通过尖括号<>设置已声明的协议,称做接受协议。可以使用逗号接受多个协议。

1
2
3
4
5
@protocol 协议名称<协议名称,协议名称>
@end

@interface 协议名称<协议名称,协议名称>
@end

协议的概念,与C++/C#/Java中的接口的概念类似

六、分类和扩展

使用分类可以在不声明子类的情况下对已存在的类进行扩展,添加功能。

分类通常用于:

  1. 扩展已定义的类(即使你无法访问它们的源码)

  2. 代替子类

  3. 将类的源码分布于多个文件(通常用于多人共同开发同一个类)

分类接口与类接口一样使用@interface指令开始,以@end指令结束。类名后跟已存在类的名称、带括号的分类名称以及所接受的协议(如果有的话)(不再标记父类)。

分类中不允许声明变量和属性

1
2
3
@interface 类名称 (分类名称)<协议名称>
//声明方法
@end

扩展是一种特殊(匿名)的分类。扩展没有名称。扩展通常位于类的主m文件中,用于组织类中独立使用的其他私有方法(如非公开API)。

1
2
3
@interface 类名称()
//声明变量、属性、方法等
@end

在扩展中声明的属性或方法必须在类的主@implementation中实现。