Programming with Objective-C(四)

本次的主要内容,是协议,在其他的高级语言中,我们通常都会有接口,主要目的就是为了定义一系列的方法让某些类实现,然后这些类就具有了一定程度的共性,这个时候我们就可以利用多态的方式,通过接口来接收这些类的对象,并且不用顾忌这些对象到底属于哪一个类,我们只需要知道它实现了当前接口定义的方法就好了,然后,通过这个对象,我们可以调用这些方法。

在 OC 中,类直接被当成了接口,所以为了实现其他语言中和接口一样的功能,苹果推出了协议。苹果把它叫做协议,是因为在苹果看来,我们在编写代码的时候,可能会遇到一些特定情况,对于这些特定的情况我们有特定的处理方式,而这些处理方式需要遵循某种制度来执行,所以将这种制度称之为协议。打个比方,在我们使用 tableView 的时候,我们需要一个数据源对象,让它来决定要显示在 tableView 中的内容,那么究竟应该显示什么内容,应该是由 tableView 发出消息,然后交给数据源对象来遵循某种规则根据接受到的消息来决定应该显示的数据。

对于这个数据源对象,其实我们并不在意它究竟是哪个对象,所以我们通常直接交给 TableViewController 来搞定。实际上,对于 tableView 来说也是如此,它并不知道将会是哪个对象来帮助自己获取数据,但是它只要确认这个接受自己消息的对象,是遵循了某种规则的,具备获取数据的功能的,就可以了。

接下来就正式说一下协议,协议的定义其实非常简单,说到底,它让我们定义了一系列的方法和属性,这些属性和方法都是独立于特定类的,所以就像开头提到的,它可以决定类的一种共性,也就是遵循了协议的类都具备这个协议所附带的一系列功能和属性。协议的声明方式如下:

@protocol ProtocolName
// List of methods and properties
@end

有一点是需要注意的,和前面一篇文章提到的扩充或者是类别都不同,首先,协议是支持方法和属性的,在这一点上它和类别就区分开了。其次,协议可以说是和类无关的,它的定义只是和我们分析时,需要的某种公共具有的行为有关,所以它和扩充也不一样,在理解的时候,并不能把协议当做是对类的一种扩展。

按照官方文档中给出的例子,我们可以用一个饼状图来说明一下。对于一个饼状图来说,它可以包含很多不同的块,然后每一块占据的空间都可能不同。那么在设计的时候,我们就需要考虑到这个视图的重用性问题了,在这里,我们的视图有两方面的功能,第一点,它要能够接受数据,因为不能接受数据那么这个饼状图就是一个写死了图。第二点就是它要能够展示数据。如果只是这么看的话,那么我们其实定义一些属性,再加上一些方法就可以实现这样的功能的,不过这样的话,我们的视图承载的功能就有些冗余了,现在用的比较多的设计模式就是 MVC,尤其是在苹果的相关开发中可以说 MVC 是无处不在。MVC 的精髓就在于把视图、数据源、和控制器分离开来,因为如果这三者混在一起,那么我们写的类是非常庞大的,并且代码容易让人感到混乱。所以,按照 MVC 的思想,我们的视图只负责显示就好了,具体的数据的获取,我们交给另一个对象去搞定,而这个对象必须得遵循一定的规则来实现,考虑到我们之前提到的所需的数据,这个协议可以这么写:

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegements;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end

接下来,在我们的饼状图中,必须得有一个属性能够追溯相应的数据,至于这个属性到底指向哪个对象,我们并不关心,所以直接使用 id 类型,也就像下面这样:

@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
...
@end

就像之前说的,我们不关心 dataSource 到底是哪个对象,我们唯一需要在意的就是它到底能否按照协议来实现我们需要的功能。

这里也就涉及到了怎么去声明一个服从某个协议的对象的问题了,在 OC 中,我们直接用一个尖括号,然后在其中写上一个协议名就可以了,这就证明这个对象是一个实现了相应协议的对象。后面如果我们把一个没有实现协议的对象赋值给了这个属性,编译器就会警告我们,所以只要声明了我们需要的是实现了某个协议的对象,在编译阶段我们就可以确定这个对象到底实现了具体的协议没有,不必担心之后会出问题。

关于协议还有一点要说,那就是协议中的方法,我们并不是非要全部实现,我们可以声明可选方法,这些方法是否实现并不重要。默认情况下,我们声明的方法都是必须,也就是如果不实现,编译器就会报错,如果要声明可选方法,可以像下面这样:

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end;

@optional 后面跟的就是可选方法,实际上,必要方法应该跟在 @required 后面,不过默认了我们就不必写出来了。

既然我们有了可选方法,那么问题也就来了,在运行时的时候我们应该怎么去调用一个可选方法?因为可选方法未必实现了,所以直接去调用是不安全的,我们必须确保这个方法已经实现了,再去使用,确保的时候,可以使用 respondsToSelector:@selector() 方法,就像下面这样:

NSString *thisSegmentTitle;
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
    this.segmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}

如果你试图对一个服从了某个协议的 id 类型的对象调用 respondToSelector 方法,那么编译器会报错,提示没有找到相应的实例方法。一旦用一个协议来修饰 id 类型,那么全部的静态类型检查都会直接返回,并且当你调用没有在特定协议中声明的方法的时候,就会报错。如果要避免这种情况,可以直接让你自定义的协议继承 NSObjects 协议。

根据上面这段话所说的,我们在定义协议的时候,其实最好的选择就是直接让我们自定义的协议去继承 NSObject 协议,主要原因是,NSObject 类的一些行为被分离在 NSObject 协议中,所以如果我们的协议没有继承 NSObject 协议的话,那么很多对应 NSObject 类的方法我们是调用不了的。协议的继承比较简单,就像下面这样:

@protocol MyProtocol <NSObject>
...
@end

最后就是关于在声明一个类的时候怎么说明这个类是符合相应协议的,其实形式很简单,就像下面这样:

@interface MyClass : NSObject <MyProtocol>
...
@end

另外,OC 的协议是可以服从多个的,只要用逗号隔开就可以了。

按照苹果的说法,如果说你的某个类,服从了大量的协议,那就很可能说明你的这个类有些冗余了,需要将它分隔成几个小类。一种比较常用的方式就是使用 delegate 来包含程序的主要功能。

官方文档中还提到了一点,就是协议的使用,除了之前提到的那些,它还可以用类的隐藏。简单来说的话,就是具体实现的类我们不希望被别人看到,所以在使用的时候,我们不会直接通过这个类来获取对象,而是交给其他的类来创建对象。在获取的时候,我们只需要确保获取到的对象具有相应的方法就可以了,就像下面的代码

id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];

基本上关于协议的用法大概就是这么一些,个人理解不深,欢迎大家指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值