iOS-Protocol

协议,一个新的obj-c事物。

协议的语法和interFace非常像。

协议是什么?

首先有几件事情要思考一下,一个是协议没有对应的@implementation,协议的实现在另一个对象里。

所以协议就是一个方法和property的集合,他的实现则是由其对象完成,非常简单。唯一语法上要注意的是,你可以有一个协议依赖于另一个协议,也就是在这个例子里,如果有人要实现协议ChouKa,那么就必须实现OuHuang和NSObject。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@property ChouKa <OuHuang, NSObject>
//implementations must implement OuHuang and NSObject too
- (void)doKeJin;
//implementations must implement this(methods are @required by default)

@optional
- (int)getSSR;
//implementations do not have to implement this
- (void)doKeJinOptionalWithArgument:(NSString *)argument;
//also optional
- (NSArray *)getManySSRs:(int)howMany;
//back to being “must implement”

@property (nonatomic, strong) NSString *chouKaProp;
//note that you must specify strength(Unless it’s readonly, of course)
@end

OuHuang和NSObject是另外的两个协议,协议NSObject基本包括了所有NSObject的方法。所以你的协议后面有个尖括号里面是NSObject,你的意思就是说ChouKa必须是个NSObject实现。所以你可以对它做内省之类的。

我们经常那么做,我想你在Xcode里输入@property会自动补上,因为几乎所有的iOS对象都是NSObject。

之后你就开始监听这个方法了,所有的方法都是必须的,除非你放到了optional里。@optional表示监听的方法是可选的,直到遇到了@required之后就变为必须的了。

所以在这个协议里,doKeJin、getManySSRs和ChouKa都是必须的(required);但是中间这两个getSSR、doKeJinOptionalWithArgument是可选的。你可以用这个协议,但是不实现这两个方法。

Q:除了NSObject还有什么可以放在协议里面?
A:答案是任何对象都可以。现实是我们一直都是从NSObject继承对象,我们一直这么做,但我们不是必须这么做,没有规定说非要这么做,但是我们就是一直这么做的。但是协议和NSObject没有直接联系。

头文件里协议的声明

它可以仅包含自己的头文件,比如KeJin.h,仅包含自己的协议,然后再import到实现和使用的地方

但是大多数情况只有一个对象会用到这里的协议,所以就把协议放到它的头文件里。比如UIScrollViewDelegate协议就是定义在UIScrollView.h里

所以你可以把协议放到其他头文件里或者自己的头文件里

现在你已经声明了协议,之后类就在他们的@interface里用<>来实现协议

1
2
3
4
5
6
7
#import “ChouKa.h”
//importing the header file that declares the Foo @protocol

@interface MyClass : NSObject <ChouKa>
//MyClass is saying it implements the Foo @protocol
...
@end

所以这个类MyClass继承自NSObject实现了协议ChouKa。

声明协议的实现

现在我必须实现ChouKa中的非可选方法,如果没有这么做,编译器会报错。编译器会说未完成的实现,必须实现所有ChouKa协议里要求的方法。

现在有了协议的声明,有了会实现协议的对象,现在我们可以有一个新的类型,它是id ,它表示一个指向未知类的对象。我可以以这种类型向这些对象发送我早协议里面的消息,而不用做任何内省,编译器会帮我检查。

所以比如id <Foo> obj = [[MyClass alloc] init]是可以的,因为在MyClass里有ChouKa的实现,所以编译器会通过;如果是id <Foo> obj = [NSArray array]就不可以,因为NSArray没有ChouKa的实现,编译会报错。

1
2
3
4
id <Foo> obj = [[MyClass alloc] init];
//compiler will love this
id <Foo> obj = [NSArray array];
//compiler will not like this one bit

我们不仅可以声明变量,还可以把他们当参数传递。所以这里的参数id ,也就是一个能够回应ChouKa方法的未知类的对象,至少是协议中必须的方法。

我们还可以有这种类型的property,比如这个myFooProperty

1
2
3
- (void)giveMeChouKaObject:(id <ChouKa>)anObjectImplementingChouKa;
@property (nonatomic, weak) id <ChouKa> myChouKaProperty;
//properties too

就像静态类型一样,所有这些东西都是为了让编译器能发现bug,在运行时都是一样的,编译器会自己去找实现方法,找到就发消息,找不到就发出exception

你可以把协议当成是你允许向这些id类型发送的消息的文档,他就是个文档,比如一个未知类型的对象,但是通过文档我知道了它实现了某个方法

delegate and datasource

在iOS里协议最主要的用途是委托(delegate)数据源(datasource)。我在最早的那篇介绍MVC的博客里讲了view从不用它的controller的类,因为view是通用的。比如UIButton内部不可能知道什么是XXXViewController,所以它需要以通用的方式对话controller,我们知道有个方法是target action,另一个方法是通过委托(delegation),委托是个重点

数据源(datasource)也是委托,只是具体到了数据的委托,记住我们的view不拥有数据,他不会有实例变量的property,而是指向数据的指针,他们可以有property表示如何去绘图,可能是颜色之类的,但是他们不拥有显示的数据,那时controller的活,因为只有controller可以对话model,model提供了view显示的数据

这有一个典型的委托声明

1
@property (nonatomic, weak) id <UISomeObjectDelegate> delegate;

委托几乎都是weak的,因为被设为委托的对象通常都是委托对象的所有者或者创造者

举个例子,controller常常把自己设为view的委托或者数据源,你不想要他们相互用strong指针互指,因为这样的话堆上的指针strong指向其他东西,这样就很难让两者同时释放。所以view只会weak指向会controller,反正view也干不了什么,如果controller不在了,view也没什么用了,所以这时候设为nil也没问题

这有个scrollView的例子,这是scrollView.h

1
2
3
4
5
6
7
8
9
10
@property UIScrollViewDelegate
@optional
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)sender;
- (void)scrollViewDidEndDragging:(UIScrollView *)sender willDecelerate:(BOOL)decelerate;
...
@end

@interface UIScrollView : UIView
@property (nonatomic, weak) id <UIScrollViewDelegate> dalegate;
@end

可以看到他声明了scrollView的委托协议,所有的方法都是可选的,我没有全列出来因为太多了。下面是正真的interFace,我们去掉了很多东西除了这个weak的property,他的类型是id ,名字是delegate。

问题是既然都是可选的,为什么我还要用协议?

再说一次,它是文档,当你说你是scrollView的委托,你就查看这个协议可以实现的方法有哪些,所以他是个文档。

举个例子,有个类想要使用scrollView,比如myViewController,注意myViewController声明了UIScrollView的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface myViewController : UIViewController <UIScrollViewDelegate>
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
@end

@implementation myViewController

- (void)setScrollView:(UIScrollView *)scrollView {
self.scrollView = scrollView;
self.scrollView.delegate = self;
//compiler won’t complain
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)sender {
return ...;
}
@end

它有一个outlet指向scrollView,它想要成为这个scrollView的委托,所以在scrollView会收到委托信息。所以比如这个scrollView的setter -> self.scrollView.delegate = self;就把self作为了scrollView的委托。现在像下面的这个viewForZoomingInScrollView这样的方法,都将会发送给myViewController

这就是协议的全部基础内容