第四章:协议与分类
Objective-C语言有一项特性叫做“协议”,类似与Java的“接口”。Objective-C不支持多重继承,所以我们把某个类应该实现的一系列方法定义在协议里面。最为常见的用途就是实现委托模式了。
“分类”也是Objective-C的一项重要特性,利用分类机制,无须继承子类即可直接为当前类添加方法。
第23条:通过委托与数据源协议进行对象间通信
- 委托模式的编程设计模式来实现对象间的通信,该模式的主旨是:
- 定义一套接口。
- 如果对象想接受另一个对象的委托,那么就要遵循这个接口,以便其称为其“委托对象”。
example:EOCDataModel
请求EOCNetworkFetcher
以异步的方式执行一项任务,而EOCNetworkFetcher
执行完这项任务之后,就会通知其委托对象,也就是EOCDataModel
。
//EOCNetworkFetcher.h
@class EOCNetworkFetcher;
//定义一个协议
@protocol EOCNetworkFetcherDelegate <NSObject>
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
@end
//有了协议之后就可以使用一个属性来存放其委托对象了。这个属性需要使用weak,而不是strong,因为DataModel和NetworkFetcher必须为非拥有关系,通常情况下,扮演delegate的对象也要持有本对象。使用weak能防止保留环的产生。
@interface EOCNetworkFetcher : NSObject
@property (nonatomic , weak) id<EOCNetworkFetcherDelegate> delegate;//利用属相来存放委托对象
@end
//EOCNetworkFetcher.m
#import "EOCNetworkFetcher.h"
@implementation EOCNetworkFetcher
-(void)didSomeSthing{
if ([_delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)]){
[_delegate networkFetcher:nil didReceiveData:nil];
}
}
@end
//EOCDataModel 实现EOCNetworkFetcherDelegate
//EOCDataModel.h
#import <Foundation/Foundation.h>
#import "EOCNetworkFetcher.h"
@interface EOCDataModel : NSObject<EOCNetworkFetcherDelegate>
@end
//EOCDataModel.m
#import "EOCDataModel.h"
@implementation EOCDataModel
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data
{
//do some things
}
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error
{
}
@end
-
委托协议一般使用
@optional
关键字来表示可选的,一般情况下都说使用这个方式来实现。 -
也可以使用协议定义一套接口,令某类经由该接口获取其所需的数据。委托模式的这一用法又称数据源模式,因为信息从数据源流向类。
-
若有必要,可以使用含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中。
第24条:将类的实现代码分散到便于管理的数个分类之中
- 如果一个类的代码全部放在一个巨大的实现文件里,可能可阅读性会差一点,那么可以通过Objective-C的分类机制,把类代码按逻辑划入几个分区中。
@interface EOCPerson : NSObject<NSCopying>
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName
andlastName:(NSString *)lastName;
//friend method
- (void)addFriend:(EOCPerson *)person;
- (void)removeFriend:(EOCPerson *)person;
- (void)isFriendWith:(EOCPerson *)person;
//work method
- (void)performDaysWork;
- (void)takevacationFromWork;
//play method
- (void)goToTheCinema;
- (void)goToSportGame;
@end
//如果利用分类的方式,可以拆成3个分类
@interface EOCPerson (Friendship)
- (void)addFriend:(EOCPerson *)person;
- (void)removeFriend:(EOCPerson *)person;
- (void)isFriendWith:(EOCPerson *)person;
@end
@interface EOCPerson (Work)
- (void)performDaysWork;
- (void)takevacationFromWork;
@end
@interface EOCPerson (Play)
- (void)goToTheCinema;
- (void)goToSportGame;
@end
- 如果编写准备分享给其他开发者使用的程序库的时候,可以创建Private分类,把私有方法归入到Private分类中。
第25条:总是为第三方类的分类名称加前缀
- 向第三方类中加分类的时候,应该添加上前缀,而对应的方法名也应该加上前缀。这样做的目的是防止因为没有加前缀而导致重复的分类被创建,或者分类下的方法命名重复,因为Objective-C没有命名空间,所以可能会出现意想不到的问题。
第26条:勿在分类中声明属性
- 除了"class-continuation分类“(拓展),其他分类都无法向类中新增实例变量。他们无法把实现属性所需的实例变量合成出来,如果使用关联对象可以解决分类不能合成实例变量的问题,但是这样代码冗余度会提高,同时可能出现内存管理的问题。
- 尽量把全部属性都定义在主接口里,而在"class-continuation分类“以外的分类,可以定义存取方法,但是尽量不要声明属性。
第27条:使用"class-continuation分类“隐藏实现细节
- Objective-C动态消息系统的工作方式决定了不可能实现真正的私有方法或私有实例变量。如果确实是不需要对外公布确应该具有的方法和实例变量应该使用"class-continuation分类“来实现。
- "class-continuation分类“和普通的分类不同,他必须定义在所拓展的那个类的实现文件里,这是唯一能声明实例变量的分类,而且此分类没有特定的实现文件。
//EOCPerson.m
@interface EOCPerson () {
//实例变量
}
//方法和属性
@end
实例变量也可以声明到实现块里,语法上和"class-continuation分类“里定义一样。
- 编写Objective-C++代码的时候,使用"class-continuation分类“也更为有效。
//EOCClass.h
#import <Foundation/Foundation.h>
#include "SomeCppClass.h"
@interface EOCClass : NSObject {
@private
SomeCppClass _cppDemo;
}
@end
假如一个类的头文件声明如上,那么其实现文件应该是EOCClass.mm
,表示告诉编译器应该将此文件使用Objective-C++的方式进行编译,但是由于头文件里包含了CPP文件,那么如果其他引入该头文件的类,都需要使用.mm
的拓展名,这样并不是非常合适。
最好的解决方案就是使用"class-continuation分类“。
//EOCClass.h
#import <Foundation/Foundation.h>
@interface EOCClass : NSObject
@end
//EOCClass.m
#import "EOCClass.h"
#include "SomeCppClass.h"
@interface EOCClass () {
SomeCppClass _cppDemo;
}
@end
@implementation EOCClass
@end
这样其头文件就不会出现CPP文件了,使用头文件的人也不会意识到这个底层的实现使用了CPP的代码,很好的隐藏了实现细节。
- "class-continuation分类“还有一种用法就是将在public接口中声明为“只读”的属性扩展为“可读写”,以便在类内部设置其值。因为这样可以不用直接访问实例变量,而是通过设置访问方法来做,就可以触发KVO通知。
@interface EOCPerson : NSObject<NSCopying>
@property(nonatomic, copy, readonly) NSString *firstName;
@property(nonatomic, copy, readonly) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)firstName
andlastName:(NSString *)lastName;
@end
@interface EOCPerson ()
@property(nonatomic, copy, readwrite) NSString *firstName;
@property(nonatomic, copy, readwrite) NSString *lastName;
@end
- "class-continuation分类“还可以定义私有的协议。有时由于对象所遵从的某个协议在某个私有API中,所以可能不太想在公共接口中泄露这一信息。
第28条:通过协议提供匿名对象
- 协议定义了一系列方法,遵从此协议的对象应该实现它们,于是可以用协议把自己所写的API之中的实现细节隐藏起来,将返回的对象设计为遵从此协议的纯id类型,这样想要隐藏的类名就不会出现在API之中了。这一概念常常被称为“匿名对象”。在委托里,经常定义匿名对象,例如:
@property (nonatomic, weak) id<EOCDelegate> delegate;
该属性是id<EOCDelegate
的,实际上任何类的对象都能充当这一属性,只需要遵循EOCDelegate
即可。
-
NSDictionary
也有应用这一概念,在可变版本的字典里,设置键值对所用的方法的签名是:
- (void)setObject:(id)object forKey:(id<NSCopying>)key;
表示键值对的参数都可以是任意类型,key参数可以视为匿名对象,字典不关心key对象的所属的具体类,字典对象只需要确定能给此实例发送拷贝消息就可以。
-
匿名对象的作用主要是:
-
隐藏类型名称。
-
对于不关心具体类型,只是关心是否能响应特定方法的情况,可以使用匿名对象。
-
网友评论