概述
OC用于拓展已存在类的内置功能是它最强大的功能之一。类目、延展、协议提供了可以让你扩展类功能的方式。使用他们,无需继承便可以扩展类功能。需要注意的是,这些手段只能增加类的方法,并不能用于增加实例变量,要想增加实例变量,还是需要定义子类来实现。
1、类目(Category):指向已知的类,增加新的方法,不会破坏封装性。已知的类既包括已定义的类,也包括系统已有的类。
2、延展(Extension):即通过在自己类的实现文件中添加类目来声明私有方法。
3、协议(Protocol):声明一些方法,但让别的类来实现,也能为类增加方法。
类目 Category
类目简介
类目也称为分类。通过定义类目,你可以为已知的类增加新的方法,哪怕是那些你没有源码的类。这是OC提供的一种强大的功能,使得你不用定义子类就能扩展一个类的功能。使用类目,你还可以将你自己的类的定义分开放在不同的源文件里。
可以为已知的类添加方法,哪怕是你没有源码的类;
通过类目添加方法会成为原始类的一部分,调用与其他方法一致;
与原类中的方法同级;
在父类中添加类目子类会继承该类目中的方法,但在子类中添加类目父类无法拥有这些方法;
把类中的方法归类,可以更好地管理和维护类;
类目的声明和实现
1、类目的命名规则:类名+扩展方法,如“ClassName+CategoryName”。类目不继承于父类,但接口与定义类很相似,用一个括号表示用途。注意定义类目的时候一定要把原类包含进来。
#import "ClassName.h"
@interface ClassName(categoryName)
// methods declarations
@end
2、实现
// 注意实现时引入的.h文件
#import "ClassName + CategoryName.h"
@implementation ClassName(CategoryName)
// methods definitions
@end
类目的创建
步骤1:在工程目录中按 command + N > 选择OS X环境 >选择 Objective-C File 模板 >点击Next进入配置界面
步骤2:在File项输入类目名 >File Type项选择文件类型(Category、Extension、Protocol)>Class项选择拓展类(Category) >点击Next
创建之后,工程目录列表如下,
Person + Handle.h
以及Person + Handle.m
文件为创建的类目文件。
类目的使用
通过类目加入的方法,会成为原始类的一部分。例如:通过类目给Person
类增加方法,编译器会把这些方法加到Person
类的定义里面。通过类目加入的方法,使用起来和原始类里面的方法没有等级差别,同等对待。类目里定义的方法会被原始类的子类所继承,就跟原始类的其他方法一样。使用类目的最大好处就是可以扩展别人实现的类,而且不需要获得原始类的源代码。但需要注意以下几点:
不能在类目中添加实例变量;
可以为同一类添加多个类目,但类目名和方法名不能重复;
如果添加的方法和系统重名,优先实现类目中的方法,因此,不能随意重写类的方法;
现在在刚刚创建的Person + Handle.h
文件中,声明方法SayHi()
。
#import "Person.h"
@interface Person (Handle)
- (void)sayHi;
@end
实现方法
#import "Person+Handle.h"
@implementation Person (Handle)
- (void)sayHi {
NSLog(@"Hi!");
}
@end
调用方法
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Handle.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person sayHi]; // 输出 Hi!
}
return 0;
}
类目的局限性
无法向类中添加实例变量,如果需要添加实例变量则只能在子类中添加;
如果在类目中重写父类方法,可能导致super消息的断裂,因为在类目中的方法优先级高于父类。
类目中的属性
在类目中不能声明实例变量,但是允许声明属性,值得注意的是,如果声明成属性,并不会生成setter( )、getter( ) 方法,并且在类目中不能使用关键字@synthesize
,只能通过@dynamic
动态合成变量和属性,在原始类中必须存在对应的实例变量;
假设在刚创建的类目中添加一个属性address
,代码如下:
#import "Person.h"
@interface Person (Handle)
@property (nonatomic, retain) NSString *address; // 类目中的属性无法生成setter、getter的实现体部分
- (void)sayHi;
@end
在main函数通过点语法调用此属性时,程序奔溃,因为系统并未生成对应的setter( )、getter( )方法,改进方法如下,首先在Person.h
文件中声明对应的实例变量_address
,代码如下:
#import <Foundation/Foundation.h>
@interface Person : NSObject {
NSString *_address;
}
@end
其次在Person + Handle.m
文件中,动态合成address
属性,并实现对应的setter( )、getter( )方法,代码如下:
#import "Person+Handle.h"
@implementation Person (Handle)
@dynamic address; //动态合成成员变量和属性,动态绑定
- (void)sayHi {
NSLog(@"Hi!");
}
#pragma mark - setter & getter
- (void)setAddress:(NSString *)address {
if (_address != address) {
[_address release];
_address = [address retain];
}
}
- (NSString *)address {
return _address;
}
@end
现在在main函数中通过类目访问address就没有问题啦。
延展 Extension
类的私有方法:
1、Objective-C中没有绝对意义上的私有方法;
2、 在.h文件中声明的方法都属于公开的方法,意味着开放给别人调用;
3、如果不想公开某些方法,可以不在.h文件中声明;
4、 这样的方法可以被本类中的其他方法所调用;
5、如果在类外面强行调用这些未公开的方法,也能调用,但是会有编译器警告;
延展简介
延展是匿名类目,为当前类添加私有方法;
使用类目增加的方法是让外部可见,而延展的目的是增加方法,让外部不可见;
不可见的目的更多的是封装代码,对于想要隐藏的算法和接口,可以使用延展;
延展接口部分写在当前类的.m文件中,实现部写在当前类的实现中;
@interface Person ()
@end
延展使用
#import "Person.h"
@interface Person ()
// 1、声明方法
- (void)sayHello;
@end
@implementation Person
// 2、实现方法
- (void)sayHello {
NSLog(@"Hello, China!");
}
- (instancetype)init {
self = [super init];
if (self) {
// 3、调用方法
[self sayHello];
}
return self;
}
@end
延展补充
- 在延展中,允许声明实例变量、属性以及方法,但是这些都是私有的,只能在对应类的实现文件访问。声明顺序如下:
@interface Person ()
{
1、实例变量声明部分
}
2、属性声明部分
3、方法声明部分
@end
协议 Protocol
协议简介
协议是一个命名的方法列表,是对象之间的交互原则与共识,一个类使用协议称作该类遵守了协议;
协议中的所有内容,只是作为一个声明,由遵守该协议的类的实现部分去实现协议方法(和我们生活中一样,如果没有人履行它,协议就是无用的纸而已);
协议可以声明属性和方法,协议的目的是告诉外部我一定会有,所以它无法用来声明全局变量,而实际上,你声明的属性和方法具体是否存在,得看你是否正确的履行了协议中的所有内容;
协议声明的属性和方法可以选择是否必须,关键字分别为@required必须和@optional可选,默认为必须;
苹果同样提供了专门的协议文件模版,它只有一个.h文件。其创建方法与延展类似,只需将File Type项选择为Protocol即可;当然还有更简便的创建方法,通过
@protocol
指令来定义一个协议,后文主要讲解通过@protocol
指令创建协议的方式。
协议的定义
@protocol ProtocolName <NSObject>
@optional
// optional methods
@required
// required methods
@end
协议的遵守
1、接受协议在某些方面与声明父类很相似:
它们都为类带来了额外的方法声明;
它们都写在类的接口的类名后;
2、当一个类把一个协议的名字列在它的父类名后的尖括号中,它即被称为“接受”了这个协议
@interface ClassName : Superclass <protocol list>
3、一个类可同时遵守多个协议,协议名之间以逗号隔开。
@interface Person : NSObject <protocol1, protocol2, protocol3...>
4、一个类实现了协议中声明的方法,称为“确认”了这个协议。协议需要被其他类所“确认”,否则这个协议就没有什么意义了。
5、可通过如下方法检查对象是否实现协议或协议方法
// 1、检查对象是否实现某个协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 2、检查对象是否实现某个方法
- (BOOL)respondsToSelector:(SEL)aSelector;
Tips
1、协议同样可以用于对对象进行类型指定。在使用协议进行类型指定是时,协议名写在类名后的尖括号中。
2、
id <MyProtocol> anyObject;
,这表明anyObject是接受了MyProtocol协议的id类型的对象。
协议下的代理委托模式
该示例涉及类为Teacher
类与Student
类,在Person类中定义一个协议,并在协议中指定买书的方法,Teacher类不直接实现买书,而是指定代理,委托Student类实现。日常生活中也会经常遇到这种情况,比如你现在正在做饭,突然发现没盐,而你不能抽身,此时你将会选择别人帮你实现下楼买盐的步骤,这就设计到代理委托模式。如下将具体讲解协议下的代理委托模式的实现。
1、第一步,在"Teacher.h"
文件中定义一个协议,指定买书方法,设置代理属性delegate,并在Teacher类的接口部分声明一个buyBooks()
方法;
#import <Foundation/Foundation.h>
// 协议声明
@protocol TeacherDelegate <NSObject>
@optional // 选择实现的
- (void)teacher:(Teacher *)teacher arrangeWork:(NSString *)work;
@required // 必须实现的
- (void)teacher:(Teacher *)teacher buyBooks:(NSArray < NSString * > *)books;
@end
@interface Teacher : NSObject
// 声明代理
@property (nonatomic, weak) id <TeacherDelegate> delegate;
- (void)doTask;
@end
2、第二步,在Teacher.m
文件中,实现buyBooks()
方法,判断代理是否存在并且实现了协议,如果代理存在并且实现了协议方法,则让代理人执行买书操作。
#import "Teacher.h"
@implementation Teacher
- (void)buyBooks {
// 判断代理人是否存在并且遵守协议
// conformsToProtocol:判断当前对象是否遵守该协议
// respondsToSelector:判断当前对象是否实现该方法
if (self.delegate && [self.delegate respondsToSelector:@selector(teacher:buyBooks:)]) {
}
if (self.delegate && [self.delegate conformsToProtocol:@protocol(TeacherDelegate)]) {
[self.delegate teacher:self buyBooks:@[@"ChineseBook", @"EnglishBook", @"MathBook"]];
}
}
@end
3、第三步,在main函数中,引入Teacher类与Student类,并实例化相应对象,将teacher对象的代理属性指定为student对象。
#import <Foundation/Foundation.h>
#import "Teacher.h"
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
#pragma mark - 协议
Teacher *teacher = [[Teacher alloc] init];
Student *student = [[Student alloc] init];
teacher.delegate = student;
}
return 0;
}
4、第四步,在Student.h
文件中,导入Teacher.h
,并遵守TeacherDelegate
协议。
#import <Foundation/Foundation.h>
#import "Teacher.h"
@interface Student : NSObject <TeacherDelegate>
@end
5、第五步,在Student.m
文件中,实现协议方法。
#import "Student.h"
@implementation Student
- (void)teacher:(Teacher *)teacher buyBooks:(NSArray<NSString *> *)books {
__block NSMutableString *string = [NSMutableString string];
[books enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[string appendFormat:@"%@、", obj];
}];
NSString *result = [string substringToIndex:string.length - 1];
NSLog(@"老师,您要买的‘%@’已经买好了。", result);
}
@end
Tips
声明delegate属性需定义成weak,而非strong,因为两者之间必须为“非拥有关系”。通常情况下,扮演delegate的那个对象也要持有本对象。例如在本例中,想使用Teacher的那个对象就会持有本对象,直到用完本对象之后,才会释放。假如声明属性的时候用strong将本对象与委托对象之间定为“拥有关系”,那么就会引入“保留环”。因此,本类中存放委托对象的这个属性要么定义成weak,用么定义成unsafe_unretained,如果需要在相关对象销毁时自动清空,则定义为前者,若不需要自动清空,则定义为后者。
6、第六步,在main函数指定teacher对象代理之后,调用buyBooks()
方法,观察控制台输出情况,输出如下:
2015-11-18 22:34:16.321 Category、ExtensionAndProtocol[1947:287990] 老师,您要买的‘ChineseBook、EnglishBook、MathBook’已经买好了。
Program ended with exit code: 0
类目与非正式协议
创建一个NSObject的类目而不实现称为“创建一个非正式协议”;
因为一般情况下类都从NSObject继承,所以NSObject的类目中所声明的方法,这个类可以实现也可以不实现;
因为NSObject的特殊性,所以NSObject的类目声明称为非正式协议