协议(protocol):
Objective-C 语言有一项特性叫做"协议",它与Java的"接口"(interface)类似。 Objective-C不支持多继承,因而我们把某个类应该实现的一系列方法定义在协议里面。 协议最为常见的用途是实现委托模式。
分类(category):
"分类"也是Objective-C的一项重要语言特性。利用分类机制,我们无须继承子类即可直接为当前类添加方法。而在其他编程语言中,则需要通过继承子类来实现。 因为Objective-C运行期系统是高度动态的,所以才能支持这一特性。
二十三、通过委托与数据源协议进行对象通信
Objective-C开发者广泛使用一种名叫"委托模式"(Delegate pattern)的编程设计模式来实现对象间通信,该模式的主旨是:定义一套接口,某对象若想接受另一个对象的委托,则需遵从此接口,以便成为其"委托对象"(delegate)。而这"另一个对象"则可以给其委托对象回传一些信息,也可以在发生相关事件时通知委托对象。
视图对象的属性中,可以包含负责数据与事件处理的对象,这两种对象分别称为"数据源"(data source)与"委托"(delegate)
委托协议名通常是在相关类名后面加上Delegate一词,整个类名采用"驼峰法"来写。
// EOCNetworkFetcher.h
@class EOCNetworkFetcher;
@protocol EOCNetworkFetcherDelegate
@required //必须实现
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
@optional //可选实现
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
@end
@interface EOCNetworkFetcher : NSObject
@property(nonatomic,weak) id <EOCNetworkFetcherDelegate> delegate; //注意weak
@end
//EOCNetworkFetcher.m
#import "EOCNetworkFetcher.h"
@implementation EOCNetworkFetcher
-(void)didSomeSthing{
//注意点:如果要在委托对象上调用可选方法,那么必须提前使用类型信息查询方法来判断这个委托对象能否响应相关选择子
if ([_delegate respondsToSelector:@selector(networkFetcher: didFailWithError:)]){
[_delegate networkFetcher:nil didFailWithError:nil];
}
//若这种检测特别频繁,可通过将其能否响应的结果缓存起来,如用类内部结构体标记。第一次标记结果之后都是检查标记值即可。
}
@end
一定要注意:这个属性需要定义成weak,而非strong,因为两者之间必须为"非拥有关系"(nonowing relationship)。通常情况下,扮演delegate的那个对象也要持有本对象。如果本对象也是strong持有扮演delegate的那个对象,就会形成保留环,互相持有,无法释放。
实现委托对象的办法是声明某个类遵从委托协议,然后把协议中想实现的那些方法在类里实现出来。而某类要遵从委托协议,可以在其接口中声明,也可以在"class-continuation分类"中声明。
//EOCDataMode.h
#import <Foundation/Foundation.h>
#import "EOCNetworkFetcher.h"
//class-continuation分类
@interface EOCDataModel : NSObject<EOCNetworkFetcherDelegate>
@end
//EOCDataMode.m
#import "EOCDataModel.h"
@implementation EOCDataModel
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data
{
//在调用delegate对象中的方法时,总应该把发起委托方的实例也一并传入方法中,这样,delegate对象在实现相关方法时,就能根据传入的实例分别执行不同的代码了。
if(fetcher == _myFetcherA){
//do some things
}else{
//do other things
}
}
-(void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error
{
//do some things
}
@end
如果要向外界公布此类实现了某协议,那么就在接口中声明,而如果这个协议是个委托协议的话,那么通常只会在类的内部使用,就在"class-continuation分类"里声明
二十四、将类的实现代码分散到便于管理的数个分类之中
同一文件内部,添加分类,用于给代码分块,是代码更清晰和易于管理
import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic , copy , readonly) NSString *firstName;
@property (nonatomic , copy , readonly) NSString *lastName;
@property (nonatomic , strong , readonly) NSArray *friends;
-(id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
@end
//分类
//@interface EOCPerson (FriendShip)
//-(void)addFriend:(EOCPerson *)person;
//-(void)removeFreind:(EOCPerson *)person;
//-(BOOL)isFriendsWith:(EOCPerson *)person;
//@end
@interface EOCPerson (work)
-(void)performDaywork;
-(void)takeVacationFromWork;
@end
@interface EOCPerson (Play)
-(void)goToTheCinema;
-(void)goToSportsGame;
@end
#import "EOCPerson.h"
@implementation EOCPerson
@end
单独的分类文件比如:
//"EOCPerson+Friends.h"
#import "EOCPerson.h"
@interface EOCPerson (Friends)
-(void)addFriend:(EOCPerson *)person;
-(void)removeFreind:(EOCPerson *)person;
-(BOOL)isFriendsWith:(EOCPerson *)person;
@end
//"EOCPerson+Friends.m"
#import "EOCPerson+Friends.h"
@implementation EOCPerson (Friends)
-(void)addFriend:(EOCPerson *)person{
}
-(void)removeFreind:(EOCPerson *)person
{
}
-(BOOL)isFriendsWith:(EOCPerson *)person
{
return YES;
}
@end
- 使用分类机制把类的实现代码划分成易于管理的小块
- 将应该视为"私有"的方法归入名叫Private的分类中,以隐藏实现细节
二十五、总是为第三方类的分类名称加前缀
#import <Foundation/Foundation.h>
@interface NSString (ABC_HTTP)
-(NSString *)abc_urlEncodedString;
-(NSString *)abc_urlDecodedString;
@end
- 向第三方类中添加分类时,总应给其名称加上你专用的前缀
- 向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀
二十六、勿在分类中声明属性
- 分类机制,目标在于扩展类的功能,而非封装数据
- 属性所要表达的意思是:类中有数据在支撑着它。属性是用来封装数据的。
- 把封装数据所用的全部属性都定义在主接口里
- 在"class-continuation分类"之外的其他分类中,可以定义存取方法,但是尽量不要定义属性。
二十七、使用"class-continuation分类"隐藏实现细节
#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
//continuation n. 继续;续集;延长;附加部分
/**
* class-continuation
* 唯一能声明实例变量的分类
*/
#import "EOCPerson.h"
@interface EOCPerson ()//<EOCSecretDelegate>
{
NSString *_anInstanceVariable;
}
@property (nonatomic , copy , readwrite)NSString *firstName;
@property (nonatomic , copy , readwrite)NSString *lastName;
-(void)p_privateMethod;
@end
@implementation EOCPerson
-(id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName{
return nil;
}
-(void)p_privateMethod{
}
- 通过"class-continuation分类"向类中新增实例变量,且这是唯一能声明实例变量的分类
- 此分类没有名字,也没有特定的实现文件,其中的方法都应该定义在类的主实现文件里
- 处理与C++混编时,将c++类引入放在此分类中,隐藏C++文件
- 扩充属性,如果某属性在主接口中声明为"只读",而在类的内部又要用设置方法修改此属性,那么就在"class-continuation"中将其扩展为"可读写"
- 把私有方法的原型声明在"class-continuation"分类
- 若想使类所遵循的协议不为人所知,则可于"class-continuation分类"中声明
二十八、通过协议提供匿名对象
#import <Foundation/Foundation.h>
@protocol EOCDatabaseConnection
-(void)connect;
-(void)disconnect;
-(BOOL)isConnected;
-(NSArray*)performQuery:(NSString*)query;
@end
/**
* 可以用协议把自己所写的API之中的实现细节隐藏起来,将返回的对象设计为遵从此协议的纯id类型,想要隐藏的类名就不会出现在API中了
*/
@interface EOCDatabaseManager : NSObject
+(id)sharedInstance;
-(id<EOCDatabaseConnection>)connectionWithIdentifier:(NSString *)identifier;
@end
@implementation EOCDatabaseManager
@end
- 协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所应事先的方法
- 使用匿名对象来隐藏类型名称(或类名)
- 如果具体类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可使用匿名对象来表示









网友评论