第23条:通过委托与数据源协议进行对象间通信
对象之间经常需要相互通信,而通信方式有很多。OC开发者广泛使用一种名叫“委托模式”(Delegate Pattern)的编程设计模式来实现对象间的通信,该模式的主旨是:定义一套接口,某对象若想接收另一个对象的委托,则需遵从此接口,以便称为“委托对象”(delegate)。而这“另一个对象”则可以给其委托对象回传一些信息,也可以在发生相关联时间时通知委托对象。
此模式可以将数据与业务逻辑解耦。
在Objective-C中,一般通过“协议”这项语言特性来实现此模式,整个Coco系统框架都是这么做的。如果你的代买也这样写,那么就能和系统框架很好地融合在一起了。
举例:
回调委托对象的流程。请注意,“委托对象”未必非得由EOCDataModel实例来担任不可,也可以由另外一个对象扮演此角色
利用协议机制,很容易就以Objective-C代码实现此模式,代码如下:
@protocol EOCNetworkFetcherDelegate -(void)netWorkFetcher:(EOCNetworkFetcher*)fetcher didReceiveData:(NSData*)data;//这个EOCNetworkFetcher*参数可以高速委托对象,是谁调用它的 -(void)netWorkFetcher:(EOCNetworkFetcher*)fetcher didFailWithData:(NSError*)error; @end
有了这个协议之后,类就可以用一个属性来存储委托对象了。在本例中,这个类就是EOCNetworkFetcher类:
@interface EOCNetworkFetcher :NSObject @property (nonatomic,weak) id<EOCNetworkFetcherDelegate> delegate; @end
一定要注意这个属性定义成weak,而非strong。因为两者之间必须为“非拥有关系”。如下图所示:
为了避免循环引用,NetWorkFetcher不保留delegate属性.
看一下EOCDataModel的实现:
@interface EOCDataModel()<EOCNetworkFetcherDelegate> @end @implement EOCDataModel -(void)netWorkFetcher:(EOCNetworkFetcher*)fetcher didReceiveData:(NSData*)data{ if(fetcher == _myFetherA){ /* handle data*/ }else if(fetcher == _myFetherB){ /* handle data*/ } } -(void)netWorkFetcher:(EOCNetworkFetcher*)fetcher didFailWithData:(NSError*)error{ /*handle error*/ } @end
这个EOCNetworkFetcher*参数可以高速委托对象,是谁调用它的.
下面是EOCNetworkFetcher调用情况
NSData *data = /*data obtained from network*/ if([_delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)]){ [_delegate networkFetcher:self didReceiveData:data]; }
通过这个例子,大家应该很容易理解此模式为何叫做“委托模式”:因为对象把应对某个行为的责任委托给另外一个类了。
关于运行时刻,每次都判断respondsToSelector:@selector(xxxx)是多余的,只有第一次判断是有用的。参考优化办法
【本节要点】
● 委托模式为对象提供了一套接口,使其可由此相关事件告知其他对象。
● 将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事情定义成方法
● 当某对象需从另一个对象中获取数据时,可以使用委托模式。这种情境下,该模式亦称为“数据源协议”(data source protocal)
● 若有必要,可实现还有位段的结构体,将委托对象是否能够相应相关协议方法这一信息缓存至其中。
第24条:将类的实现代码分散到便于管理的数个分类之中
类中经常容易填满各种方法,而这些方法的代码则全部堆在一个巨大的实现文件里。在此情况下,可以通过Objective-C的“分类”机制,把类代码按照逻辑划入几个分区中。
Cocoa中的NSURLRequest类以及可变版本NSMutableURLRequest类就是这么做的。
另外:在编写准备分享给其他开发者使用的程序库时,可以考虑建立“Private”分类,如果程序中的某个地方要用到这些方法,那就引入此分类的头文件。而分类头文件并不会随程序库一并公开,于是该库的使用者也就不知道库里还有这些私有方法了。
第25条:总是为第三方类的分类名称前加前缀
分类机制通常用于向无源码的既有类中添加新功能。这个特性极为强大,但是使用时候很容易忽视产生的问题。分类中的方法是直接添加到类里面的,如果分类中的方法跟类中本来就有的方法重名了,那么就会覆盖掉固有的方法了。
你可能这么写分类
@interface NSString (HTTP) //Encode a string with URL encoding -(NSString*)urlEncodeString; //Decode a URL encoded string -(NSString*)urlDecodedString; @end
如果NSString类本身就有urlEncodeString方法,或者还有另外一个分类也叫urlEncodeString,如果别人写的分类加载时间比你晚的时候,你的将被覆盖。想要解决此问题,以命名空间来区分各个分类的名称
@interface NSString (ABC_HTTP) //Encode a string with URL encoding -(NSString*)abc_urlEncodeString; //Decode a URL encoded string -(NSString*)abc_urlDecodedString; @end
第26条:勿在分类中生命属性
属性是封装数据的方式。尽管从技术上说,分类里可以生命属性,但这种做法还是要尽量避免。
第27条:使用“class-continuation”隐藏实现细节
(待补充)
第28条:通过协议提供匿名对象
协议定义了一系列方法,遵从此协议的对象应该实现它们。于是,我们可以用协议把自己写的API之中的实现细节隐藏起来,将返回的对象涉及位遵从此协议的纯id类型。这样的话,想要隐藏的类名就不会出现在API之中了。
此概念经常称为:“匿名对象”(anonymous object),这与其他语言的“匿名对象”不同。
@property (nonatomic,weak)id<EOCDelegate> delegate;
由于该属性的类型是id<EOCDelegate>,所以实际上任何类型的对象都能充当这一属性,即便该类不继承自NSObject也可以,只要遵循EOCDelegae协议就行。
NSDictionary也能实际说明这一概念,在字典中,键的标准内存管理语义是“设置时拷贝”,而值的语义是“设置时保留”。因此在可变版本的字典中,设置键值对所用的方法的签名是:
-(void) setObject:(id)object forKey:(id<NSCopying>)key;
表示键的哪个参数类型位id<NSCopying>,作为参数值的对象,它可以是任意类型,只要遵从NSCopying协议就好,这样的话,就能向该对象发送拷贝消息了。这个key参数可以视为匿名对象。与delegate一样
数据案例,数据库连接(database connection)的程序也用这个思路,以匿名对象来表示从另一个库中返回的对象。对于处理连接哪个类,你也许不想让万人知道。如果没有办法令其继承字同一个基类,那么就得返回对下你跟遵从此协议:
@protocol EOCDatabaseConnection -(void) connect; -(void)disconnect; -(void)isConnected; -(NSArray*)performQuery:(NSString*)query; @end;
然后,就可以用“数据库处理器”单例来提供数据库连接了。这个单例的接口可以写成:
@protocol EOCDatabaseConnection @interface EOCDatabaseManger:NSObject +(id)sharedInstance; -(id<EOCDatabaseConnection>) connectionWithIdentifier:(NSString*)identifier; @end;
这样的话,处理数据库连接所用的类名称几UI不会泄露了。
有时对象类型并不重要,重要的是对象有没有实现某些方法。在次情况下,也可以是使用“匿名类型”(anonymous type)来表达这一概念。
CoreData框架里也有这种用法。查询CoreData数据库所得的结果由名叫NSFetchedResultsContrller的类来处理,如果有需要,处理时还会把数据分区。在负责处理查询结果的控制器中,有个section属性,用以表示数据分区。此属性是个数组,但其中的对像没有指明具体类型,只是说这些对象遵从了NSFetchedResultsSectionInfo协议。下面代码通过控制器来获取数据分区信息:
NSFetchedResultsController * controller = /*some controller*/; NSUInteger section = /*section index to query*/ NSArray *sections = controller.sections; id<NSFetchedResultsSectionInfo> sectionInfo = sections[section]; NSUInteger numberOfObjects = sectionInfo.numberOfObjects;
sectionInfo 是个匿名对象。
【本节要点】
● 协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所应实现的方法。
● 使用匿名对象来隐藏类型名称(或类名)
● 如果居室类型不重要,重要的是对象能够响应特定方法,那么可以是使用匿名对象来表示。
《Effective Objective-C 2.0》—(第23-28条)—类别、协议,代理,匿名对象、delegate