【Effective Objective-C 2.0读书笔记】第四章:协议和分类

来源:互联网 发布:手机视频融合软件 编辑:程序博客网 时间:2024/05/16 06:30

Objective-C中的“协议”(protocal)类似于java中的接口。由于Objective-C不支持多重继承,因此我们把某个类应该实现的一些列方法定义在协议里。协议最为常见的用途是实现委托模式。不过也有其他用法。理解并善用协议,可以令代码更易维护,因为协议这种方式能很好地描述接口。

“分类”(category)也是Objective-C中一项重要的语言特性。利用分类机制,可以无须继承子类即可直接为当前类添加方法,而在其他编程语言中,则需要通过继承子类来实现。由于Objective-C运行期系统是高度动态的,故才能支持这一特性。然而,其中也隐藏一些陷阱,因此在使用分类前,应该先理解它。

第23条:通过委托与数据源协议进行对象间通信

对象之间经常需要相互通信,而通信方式有多种。Objective-C开发者广泛使用“委托模式”(Delegate pattern)的编程设计模式来实现对象间的通信,该模式的主旨是:定义一套接口,某对象若想接受另一个对象的委托,则需遵从此接口,以便成为其委托对象。而此处的“另一个对象”则可以向委托对象回传信息,也可以在发生相关事件时通知委托对象。

此模式可将数据与业务逻辑解耦,例如视图对象的属性中,可以包含负责数据与事件处理的对象,分别称为“数据源”(data source)和“委托”(delegate)。在Objective-C中,一般通过“协议”来实现该模式,整个Cocoa系统框架都是这么做的。如果你的代码也这么写,就能与系统框架很好地融合在一起。

委托协议中的方法一般都是可选的,因为扮演“受委托者”角色的这个对象未必关心其中的所有方法。为了指明可选方法,经常使用@optional关键字来标注。

如果要在委托对象上调用可选方法,那么必须提前使用类型信息查询方法来判断其是否能够响应相关选择子:

if([_delegate respondsToSelector: @selector(methodName:)]) {    [_delegate methodName:parameters];}

很容易用代码查出某个委托对象是否能响应特定的选择子,可是如果频繁执行此操作,那么除了第一次检测结果有用,后续的检测可能都是多余的。有鉴于此,我们通常把委托对象能否响应某个协议方法这一信息缓存起来,以优化程序效率。将方法响应能力缓存起来的最佳途径是使用“位段”(bitfield)数据类型。这是一项乏人问津的C语言特性,但在此处用起来却正为合适。

以笔者自定义的网络数据获取器类为例,可以嵌入一个含有位段的结构体作为其实例变量,而结构体的每个位段则表示delegate对象是否实现了协议中的相关方法。

@interface EOCNetworkFetcher () {    struct {        unsigned int didReceiveData      : 1;        unsigned int didFailWithError    : 1;        unsigned int didUpdateProgressTo : 1;    } _delegateFlags;}@end

上述代码中的结构体用来缓存委托对象能否响应特定的选择子,实现缓存功能的代码可以写在delegate属性所对应的设置方法里:

- (void)setDelegate: (id<EOCNetworkFetcher>) delegate {    _delegate = delegate;    _delegateFlags.didReceiveData = [_delegate respondsToSelector: @selector(networkFetcher:didReceiveData:)];    _delegateFlags.didFailWithError = [_delegate respondsToSelector: @selector(networkFetcher:didFailWithError:)];    _delegateFlags.didUpdateProgressTo = [_delegate respondsToSelector: @selector(networkFetcher:didUpdateProgressTo:)];}

这样,每次调用delegate的相关方法之前,就不需要检测委托对象是否能响应相关选择子,而是直接查询结构体里的标志。

if(_delegateFlags.didUpdateProgressTo) {    [_delegate networkFetcher:self didUpdateProgressTo:currentProgress];}

是否需要这种优化,应根据具体代码来定。这就需要分析代码性能,找出瓶颈,看这种优化技术是否能够较大地提高程序效率。

要点:

委托模式为对象提供了一套接口,使其可由此将相关事件告知其他对象。

当某个对象需要从另外一个对象中获取数据时,可以使用委托模式。这种情景下,该模式亦称“数据源协议”(data source protocal)。

若有必要,可实现含有位段的结构体,将委托对象能否响应相关协议方法这一信息缓存至其中。

第24条:将类的实现代码分散到便于管理的若干个分类之中

通过Objective-C的“分类”(category)机制,把类代码按逻辑划入若干个分区中,这对开发与调试都有好处。

使用分类机制,可以把类的实现代码划分成易于管理的小块。

分类机制为调试提供了便利:对于某个分类中的所有方法来说,在调试器的回溯信息里,分类名称都会出现在其符号中。

将应该视为“私有”的方法归入名为Private的分类中,以隐藏实现细节。

第25条:总是为第三方类的分类名称加前缀

分类常用于对那些你不能拥有其源代码的已存在的类,增添新的功能。这是一个很强大的功能,但人们经常会忽略一个问题:在分类于运行时中被加载时,分类中的方法被添加到类的方法列表中,正如类自身的一部分;之后加载的分类中的方法会覆盖先前加载的分类中的同名方法,最后覆盖主类实现代码的同名方法。

克服这个问题的典型方法是为分类名称和方法名称添加命名空间。在Objective-C中添加命名空间的唯一方式就是在名称前面添加前缀。例如:

@interface NSString (ABC_HTTP)// Encode a string with URL encoding- (NSString*)abc_urlEncodedString;// Decode a URL encoded string- (NSString*)abc_urlDecodedString;@end

第26条:勿在分类中声明属性

属性是封装数据的一种方式。尽管在分类中声明属性在技术上来说是可行的,但是你应该避免这样做。原因在于不能在分类中添加实例变量,除非是在class-continuation分类中。

要点:

把封装数据所用的全部属性都定义在主接口里。

在“class-continuation分类”之外的其他分类中,可以定义存取方法,但尽量不要定义属性。

第27条:使用“class-continuation分类”隐藏实现细节

在开发中,经常需要将一些实例变量和方法设置成私有的,以避免将它们直接暴露给用户。但是鉴于Objective-C的动态消息机制,我们无法实现实例变量和方法的真正私有化。尽管可以在公共接口中将它们声明为private,但仍然泄露了其实现细节。使用class-continuation分类可以将实例变量和方法声明为私有。

class-continuation分类指的是在类的实现文件中定义的特殊分类。它是唯一可以在其中声明实例变量的分类,并且没有专门针对它的实现文件,在其中声明的方法都将出现在类的主实现里。不像其他分类,它没有名字。

在class-continuation分类和主实现里都可以声明实例变量,只在内部可知,向用户隐藏实现细节,用法如下:

@interface EOCClass () {    NSString _anInstanceVariable;}// Method declarations here@end@implementaion EOCClass {    int _anotherInstanceVariable;}// Method implementations here@end

使用class-continuation分类的一个特别有用的地方是使用Objective-C++一起混合编程时。Objective-C与C++混合编程的场景有很多:为了方便移植,游戏后端代码一般都采用C++来编写;另外,当使用拥有C++绑定的第三方库时,也需要采用C++。但需要注意的是,当你在类的头文件中include或import一个C++的头文件时,该类的实现文件就不能为.m文件扩展后缀了,而必须是.mm后缀,即要改成Objective-C++源文件。如果持续这样处理其他类,则整个应用程序都变成Objective-C++的了。避免这种情况发生的方法是在class-continuation分类中导入C++头文件,这样可以保证该类的头文件是纯Objective-C代码。

使用class-continuation分类的另一个好处是当在公共接口中将某属性声明为readonly后,可以在class-continuation分类里面将该属性改为可读(readwrite)。出现在class-continuation分类、其他分类或者类接口中的同名属性都必须拥有相同的特性,唯一一个特例是可以将属性从readonly状态更改为readwrite

使用class-continuation分类的另一个有用的地方是声明只在类的实现里被使用的私有方法,用法如下:

@interface EOCClass- (void)p_privateMethod;@end

最后,class-continuation分类还适于声明类遵从那些被认为是私有的协议,这样就不会向外部显式泄漏关于这些协议的相关信息,用法如下:

#import "EOCClass.h"#import "EOCSecretDelegate.h"@interface EOCClass ()<EOCSecretDelegate>@end@implementation EOCClass/*...*/@end

要点:

通过“class-continuation分类”向类中新增实例变量。

如果某属性在主接口中声明为“只读”,而类的内部又要用设置方法修改此属性,那么就在“class-continuation分类”中将其扩展为“可读写”。

把私有方法的原型声明在“class-continuation分类”里面。

若想使类所遵循的协议不为人所知,则可于“class-continuation分类”中声明。

第28条:通过协议提供匿名对象

Objective-C中的匿名对象含义与其他编程语言中所指的匿名对象有所不同,其他编程语言中的匿名对象指的是在不提供类名的情况下来创建inline类。

协议可以被用来声明匿名对象的接口,也就是事先不知道其类型的对象的接口。

要点:

协议可在某种程度上提供匿名类型,形如@property (nonatomic, weak) id<EOCDelegate>delegate;,隐藏了具体的类名。具体 对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所应实现的方法。

使用匿名对象来隐藏类型名称(或类名)。

如果具体类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可使用匿名对象来表示。

0 0