第一种用途
类中经常会包含一些无须对外公布的方法及实例变最。其实这些内容也可以对外公布, 并且写明其为私有,开发者不应依赖它们。但是OC
的动态性,使得不可能实现真正的私有方法或私有实例变量。
但是我们最好还是只把确实需要对外公布的那部分内容公开。那么,这种不需对外公布但却应该具有的方法及实例变量应该怎么写呢?此时,这个特殊的“class-continuation分类”
就派上用场了。
“class-continuation分类”和普通的分类不同,它必须定义在其所接续的那个类的实现文件里。
例如:
//EOCPerson.m
@interface EOCPerson () {
NSString * _anInstanceVariable;
}
// Method declarations here
@end
@implementation EOCPerson {
int _anotherInstanceVariable;
}
// Method implementations here
@end
我们这样定义的目的是将这些方法或者实例变量隐藏起来,只供本类使用。即便在公共接口里将其标注为private,也还是会泄漏实现细节。
例如有个绝密的类,不想给其他人知道。 假设你所写的某个类拥有那个绝密类的实例,而这个实例变量又声明在公共接口里面:
#import <Foundation/Foundation.h>
@class EOCSuperSecretClass;
@interface EOCClass : NSObject {
@private
EOCSuperSecretClass *_secretInstance;
@end
这样别人就会知道有一个叫EOCSuperSecretClass
的类了。
所以我们通常应该这样:
// EOCClass.h
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
@end
// EOCClass .m
#import "EOCClass.h"
#import "EOCSuperSecretClass.h"
@interface EOCClass () {
EOCSuperSecretClass *_secretInstance;
@end
@implementation EOCClass
// Methods here
@end
第二种用途
编写Objective-C++
代码时 “class-continuation分类”
也很有用。Objective-C++
是 OC
与C++
的混合体,其代码可以用这两种语言来编写。由于兼容性原因,游戏后 端一般用C++
来写。另外,有时候要使用的第三方库可能只有C++
绑定,此时也必须使用 C++
来编码。在这些情况下,使用"class-continuation分类"
会很方便。假设某个类打算这样写:
#import <Foundation/Foundation.h>
#include "SomeCppClass.h"
@interface EOCClass : NSObject {
@private
SomeCppClass _cppClass;
@end
该类的实现文件可能叫做EOCClass.mm
,其中.mm
扩展名表示编译器应该将此文件按Objective-C++
来编译,否则,就无法正确引人SomeCppClass.h
了。然而请注意,名为SomeCppClass
的这个C++
类必须完全引入,因为编译器要完整地解析其定义方能得知_cppClass
实例变量的大小。于是,只要是包含EOCClass.h
的类,都必须编译为 Objective-C++
才行,因为它们都引入了SomeCppClass
类的头文件。这很快就会失控,最终 导致整个应用程序全部都要编译为ObjeCtive-C++。这样显然会增加编码的负担。
也许我们会想用前向声明来避免导入SomeCppClass.h
,比如:
#import <Foundation/Foundation.h>
class SomeCppClass;
@interface EOCClass : NSObject {
@private
SomeCppClass *_cppClass;
@end
现在实例变量必须是指针,若不是,则编译器无法得知其大小,从而会报错。但所有指针的大小确实都是固定的,于是编译器只需知道其所指的类型即可。
虽然我们这样做没有#include "SomeCppClass.h"
但是我们前向声明该类时所用的class
关键字还是C++
下的关键字,所以仍然需要按照OC
来编译才行。
我们这里的解决方法还是一样,既然变量是private
的,我们还是可以将它在“class-continuation分类”
声明,改写成:
// EOCClass. h
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
// EOCClass.mm
#import "EOCClass.h"
#include "SomeCppClass.h"
@interface EOCClass () {
SomeCppClass _cppClass;
}
@end
@implementation EOCClass
@end
改写后的EOCClass
类,其头文件里就没有C++
代码了,使用头文件的人甚至意识不到其底层实现代码中混有C++
成分。某些系统库用到了这种模式,比如网页浏览器框架WebKit
,其大部分代码都以C++
编写,然而对外展示出来的却是一套整洁的Objective-C
接口。CoreAnimation
里面也用到了此模式,它的许多后端代码都用C++
写成,但对外公布的却是一套纯Objective-C
接口。
第三种用法
就是将public
接口中声明为“只读”的 属性扩展为“可读写”,以便在类的内部设置其值。
例如:
// .h文件
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
-(id) initWithFirstName : (NSString*) firstName
lastName: (NSString*) lastName;
@end
我们一般会在“class-continuaticm分类”
中把这两个属性扩展为“可读写”:
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
-(void)p_privateMethod;
@end
只需要用上面几行代码就行了。现在EOCPerson
的实现代码可以随意调用“setFirstName:”
或“setLastName:”
这两个设置方法,也可以用“点语法”来设置属性。这样做很有用,既能令外界无法修改对象,又能在其内部按照需要管理其数据。
只会在类的实现代码中用到的私有方法也可以声明在“class-continuation分类”
中。这么做比较合适,因为它描述了那些只在类实现代码中才会使用的方法。上述的私有方法加上了p_
前缀。
第四种用法
当我们想要把对象所遵守的协议视为私有,就可以在“class-continuation分类”
中声明。例如:
#import "EOCPerson•h"
#import "EOCSecretDelegate.h"
@interface EOCPerson () <EOCSecretDelegate>
@end
@implementation EOCPerson
/*.....*/
@end
要点
通过
“class-continuation分类”
向类中新增实例变量。如果某属性在主接口中声明为“只读”,而类的内部又要用设置方法修改此属性,那么就在
“class-continuation分类”
中将其扩展为“可读写”把私有方法的原型声明在
“class-continuation分类”
里面。若想使类所遵循的协议不为人所知,则可于
“class-continuation分类”
中声明。