假如你要学习Objective-C,那么你需要搞清楚interface、protocol、category以及extension这几个概念,它们在代码的出现频率非常高,也非常有用。本文将对这几个概念一一来说明,并介绍各自的使用方法。
Interface
interface在Objective-C里面充当头文件作用,在Xcode里面interface是以*.h为后缀的文件,在里面可以定义类文件所需的成员变量、getter/setter以及函数方法,但是这些声明要放到@interface … @end
指令中间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| #import <Foundation/Foundation.h>
@interface Mobile : NSObject
{
NSString *name;
@private
NSString *model;
@protected
NSString *sn;
@public
NSString *producer;
}
@property NSString *photoNumber;
@property NSString *mail;
+(Mobile *) create;
-(void) call;
@end
|
在这个头文件里面定义了四个成员变量name、model、sn、producer,其中name、model是私有变量,sn是protected变量,producer为public变量;photoNumber、mail是getter/setter函数;前面有个减号的call是一个实例方法,前面有个加号的create是一个类方法,与Actionscript的public静态方法类似。
interface头文件声明的方法需要在@implementation … @end
指令里面实现,如下
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
| #import "Mobile.h"
@implementation Mobile
@synthesize photoNumber;
@synthesize mail;
-(id)init
{
self = [super init];
if(self)
{
self->name = @"iPhone";
self->model = @"A1332";
self->producer = @"Apple Inc.";
}
return self;
}
+(Mobile *)create
{
return [[Mobile alloc] init];
}
-(void)call
{
NSLog(@"Call %@", self.photoNumber);
}
@end
|
成员变量可以通过self->
来访问,不管该变量是private还是protected、public。相比之下,如果要访问getter/setter方法photoNumber
,那么可以有三种方式:
1
2
3
| NSLog(@"Call %@", self.photoNumber);
NSLog(@"Call %@", self->photoNumber);
NSLog(@"Call %@", [self photoNumber]);
|
call
是实例方法,我们可以构造一个Mobile
实例来调用,如下
1
2
3
| Mobile *mobile = [[Mobile alloc] init];
mobile.photoNumber = @"15988886039";
[mobile call];
|
而create
是类方法,可以通过类名直接访问,如下
1
2
3
| Mobile *mobile = [Mobile create];
mobile.photoNumber = @"15988886039";
[mobile call];
|
Protocol
protocol也是一个*.h文件,通过@protocol … @end
来声明接口(函数方法或者getter/setter),跟interface有点类似。它有什么用呢?
- 声明一个隐藏类的调用接口
- 在非继承关系的类之间可以做类型转换
这个与Actionscript的interface文件作用是一致的,任何类都可以去实现这个protocol接口,然后具有protocol声明的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #import <Foundation/Foundation.h>
@protocol IMobile <NSObject>
@property NSString *photoNumber;
-(void) call;
@required
-(void) playMusic;
@optional
-(void) restart;
@end
|
IMobile
接口声明了两个必须实现的接口call、playMusic
,一个可选接口restart
,也是restart可以选择不实现。实现这个protocol接口,可以在interface文件中添加一个尖括号来声明,如下
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
| #import <Foundation/Foundation.h>
#import "IMobile.h"
@interface Mobile : NSObject <IMobile>
{
NSString *name;
@private
NSString *model;
@protected
NSString *sn;
@public
NSString *producer;
}
@property NSString *photoNumber;
@property NSString *mail;
+(Mobile *) create;
-(void) call;
@end
|
然后通过@implementation … @end
来实现,IMobile
声明了getter/setter方法photoNumber
以及call、playMusic
两个实例方法,由于photoNumber、call
已经被Mobile
实现,所以只需要实现playMusic
即可。如下
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
| #import "Mobile.h"
@implementation Mobile
@synthesize photoNumber;
@synthesize mail;
-(id)init
{
self = [super init];
if(self)
{
self->name = @"iPhone";
self->model = @"A1332";
self->producer = @"Apple Inc.";
}
return self;
}
+(Mobile *)create
{
return [[Mobile alloc] init];
}
-(void)call
{
NSLog(@"Call %@", [self photoNumber]);
}
-(void)playMusic
{
NSLog(@"Play music.");
}
@end
|
protocol接口实现后,可以这样来使用
1
2
3
4
| id <IMobile> mobile = [[Mobile alloc] init];
[mobile setPhotoNumber:@"15988886039"];
[mobile playMusic];
[mobile call];
|
或者
1
2
3
4
5
6
| Mobile <IMobile> *mobile = [[Mobile alloc] init];
mobile.photoNumber = @"15988886039";
[mobile setPhotoNumber:@"15988886039"];
[mobile playMusic];
[mobile call];
|
上面说到restart
为可选接口,有可能没有被实现,在没有实现的情况下去调用就会报错,对于这种接口就需要在使用前检测一下是否可用,如下
1
2
3
4
5
6
7
8
9
| SEL restart = @selector(restart:);
if([mobile respondsToSelector:restart])
{
[mobile restart];
}
else
{
NSLog(@"restart is not complement.");
}
|
注意:上面提到@required
指令标记的方法必须实现,不然会出现编译警告
Category
category可以对一个类文件添加方法,哪怕不知道这个类的代码,只要有这个类的interface头文件即可。这个功能有什么用呢?举个简单例子就可以很容易理解。
category interface
1
2
3
4
5
| #import <Foundation/Foundation.h>
@interface NSObject (Run)
-(void) run;
@end
|
category implementation
1
2
3
4
5
6
7
8
| #import "NSObject+Run.h"
@implementation NSObject (Run)
-(void)run
{
NSLog(@"Run! ::%@", [self className]);
}
@end
|
category使用实例
1
2
3
4
5
6
7
8
| NSString *name = @"larryhou";
[name run];
Mobile <IMobile> *mobile = [[Mobile alloc] init];
mobile.photoNumber = @"15988886039";
[mobile playMusic];
[mobile call];
[mobile run];
|
有没有发现什么情况?
我在NSObject+Run.h头文件定义了一个run
的方法,然后在NSObject+Run.m文件中实现run
方法,然后再#import “NSObject+Run.h”
,然后奇迹发生了:所有继承了NSObject的类都好像有了run
这个方法一样!!!是的,没错就是这样!是不是很酷!!
细心的同学可能已经发现,定义category需要写一个interface文件,@interface
指令后面紧跟被扩展类名,然后写在圆括号里面写入category名称,可以为任意字符串。在实现categor的interface的时候也要注意:@implementation
指令后面紧跟被扩展类名,然后在圆括号里面写入category名称,与interface声明保持一致,其他与实现普通类的interface方式一样。
这样再不修改源代码的情况下,给一个类添加方法还可以有另外一个用途:把类逻辑拆分到不同的文件里面,也就是说一个类可以有多个文件组成。
前面讲到IMobile
有一个@optional
标记的方法restart
,现在我们把restart
声明去掉,通过category方式来扩展Mobile
的功能。
Mobile
扩展头文件
1
2
3
4
5
6
7
| #import "Mobile.h"
@interface Mobile (Restart)
-(void) restart;
@end
|
Mobile
扩展实现
1
2
3
4
5
6
7
8
9
10
| #import "Mobile+Restart.h"
@implementation Mobile (Restart)
-(void)restart
{
NSLog(@"Restart mobile phone.");
}
@end
|
Mobile
扩展使用实例
1
2
3
4
5
| Mobile *mobile = [[Mobile alloc] init];
mobile.photoNumber = @"15988886039";
[mobile playMusic];
[mobile restart];
[mobile call];
|
这个功能有点像代码注入,感觉非常棒!\(^o^)/~
Extension
我在Mobile.m文件里面添加如下一段代码
1
2
3
4
5
6
7
| @interface Mobile ()
@property NSInteger *size;
-(void) sleep;
@end
|
然后在Mobile.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
| #import "Mobile.h"
@interface Mobile ()
@property NSInteger *size;
-(void) sleep;
@end
@implementation Mobile
@synthesize photoNumber;
@synthesize size;
@synthesize mail;
-(id)init
{
self = [super init];
if(self)
{
self->name = @"iPhone";
self->model = @"A1332";
self->producer = @"Apple Inc.";
}
return self;
}
+(Mobile *)create
{
return [[Mobile alloc] init];
}
-(void)call
{
NSLog(@"Call %@", self.photoNumber);
NSLog(@"Call %@", self->photoNumber);
NSLog(@"Call %@", [self photoNumber]);
}
-(void)playMusic
{
NSLog(@"Play music.");
[self sleep];
}
-(void)sleep
{
NSLog(@"Sleep.");
}
@end
|
然后调用sleep、size
看看发生了什么
报错了!这是为什么?
这段接口声明看起来好像是跟Mobile.h文件是重复的,其实是对Mobile.h的补充,但是@interface Mobile ()
有个没有任何内容的圆括号,那么在这种补充模式下声明的接口是私有的不能在类文件意外调用,在类文件内部是可以使用的,如下
1
2
3
4
5
6
| -(void)playMusic
{
NSLog(@"Play music.");
[self sleep];
}
|
到这里interface、protocol、category和extension这四个概念都已经介绍完了,搞清楚了再去学习Objective-C基本上就是如鱼得水了!GOOD LUCK!