iOS 中协议是什么

126 阅读7分钟

我们来详细地讲解一下 Objective-C 中的协议(Protocol)。

一、什么是协议(Protocol)?

在 Objective-C 中,协议是一种定义了“行为契约”的语言特性。它本质上是一个方法声明的列表,任何类(或分类、甚至其他协议)都可以表示自己愿意遵守这份契约。如果一个类遵守了某个协议,它就承诺会实现该协议中声明的方法。

您可以将协议理解为:

  • 一份合同或蓝图:它规定了需要完成哪些任务(方法),但它不关心由谁来完成(哪个类),也不关心具体怎么完成(方法的内部实现)。
  • 一种“水平”扩展能力的方式:类的继承是“垂直”的,子类继承父类的所有东西。而协议是“水平”的,任何不相关的类都可以遵守同一个协议,从而拥有共同的行为接口,这也被称为“组合优于继承”思想的一种体现。
  • 类似于其他语言中的“接口(Interface)”:如果您熟悉 Java、C# 或 Swift,Objective-C 的协议与它们的接口概念非常相似。

它的核心作用是实现关注点分离和代码解耦

二、协议的规定(语法和规则)

协议的定义和使用有一套清晰的语法规则。

1. 声明协议

使用 @protocol@end 关键字来声明一个协议。

@protocol MyProtocolName
// 方法声明列表
@end

2. 方法的规定:@required@optional

协议中的方法可以被标记为必须实现可选实现

  • @required (默认):该关键字之后声明的方法,遵守此协议的类必须实现它们,否则编译器会发出警告。这是默认行为,所以即使不写 @required,方法也是必须实现的。
  • @optional:该关键字之后声明的方法,遵守此协议的类可以不实现它们。在调用这些可选方法之前,必须先检查对象是否响应这个方法。
@protocol DownloaderDelegate

@required // 这是必须实现的(也是默认的)
- (void)downloadDidFinish:(NSData *)fileData;
- (void)downloadDidFailWithError:(NSError *)error;

@optional // 这是可选实现的
- (void)downloadDidUpdateProgress:(float)progress;

@end

3. 属性的规定

协议不仅可以声明方法,还可以声明属性。在协议中声明属性,实际上是要求遵守协议的类必须声明对应的存取方法(accessor methods),即 gettersetter

@protocol Identifiable
// 要求遵守者必须拥有一个只读的'uuid'属性
@property (nonatomic, strong, readonly) NSString *uuid;

// 要求遵守者必须拥有一个可读写的'name'属性
@property (nonatomic, copy) NSString *name;
@end

当一个类遵守 Identifiable 协议时,它必须为 uuid 提供一个 getter 方法,并为 name 提供 gettersetter 方法。通常,我们通过在类中声明并合成(@synthesize)同名属性来满足这一要求。

4. 协议的继承

一个协议可以继承自一个或多个其他协议。这会将父协议的所有方法要求(包括可选和必须)都继承过来。

// BaseProtocol继承自NSObject协议,这是大多数协议的惯例
@protocol BaseProtocol <NSObject>
- (void)baseMethod;
@end

// AdvancedProtocol继承自BaseProtocol
@protocol AdvancedProtocol <BaseProtocol>
- (void)advancedMethod;
@end

任何遵守 AdvancedProtocol 的类,都必须同时实现 baseMethodadvancedMethod

三、如何使用协议

使用协议主要分为三个步骤:类遵守协议、类实现协议方法、使用协议作为类型。

1. 类遵守协议

一个类可以在其 @interface 声明中,通过尖括号 <...> 来表示它遵守一个或多个协议。

#import "DownloaderDelegate.h"

// MyViewController 类继承自 UIViewController,并遵守 DownloaderDelegate 协议
@interface MyViewController : UIViewController <DownloaderDelegate>

// ...

@end

2. 类实现协议方法

在类的 @implementation 部分,必须实现协议中 @required 的所有方法。@optional 的方法则根据需要实现。

@implementation MyViewController

#pragma mark - DownloaderDelegate Methods

// 实现必须的方法
- (void)downloadDidFinish:(NSData *)fileData {
    NSLog(@"下载完成,文件大小: %lu bytes", (unsigned long)fileData.length);
    // 处理下载好的数据
}

- (void)downloadDidFailWithError:(NSError *)error {
    NSLog(@"下载失败,错误: %@", error.localizedDescription);
    // 展示错误提示
}

// 选择性地实现可选方法
- (void)downloadDidUpdateProgress:(float)progress {
    NSLog(@"下载进度: %.2f%%", progress * 100);
    // 更新UI进度条
}

@end

3. 使用协议作为类型(核心用途)

这是协议最强大的地方,它允许我们编写与具体类无关的通用代码。

a. 委托模式(Delegation Pattern)

这是协议在 Cocoa 和 Cocoa Touch 框架中最常见的应用。一个对象(委托方)将某些任务或决策的权力委托给另一个对象(代理方)。协议就是它们之间的通信合同。

示例:一个 FileDownloader 类负责下载,但它不关心如何展示进度或处理结果。它只通过 DownloaderDelegate 协议与它的代理通信。

// FileDownloader.h
#import "DownloaderDelegate.h"

@interface FileDownloader : NSObject

// 声明一个代理属性。类型是 id<DownloaderDelegate>
// id 表示是任意对象
// <DownloaderDelegate> 表示这个对象必须遵守 DownloaderDelegate 协议
@property (nonatomic, weak) id<DownloaderDelegate> delegate;

- (void)startDownloadWithURL:(NSURL *)url;

@end

// FileDownloader.m
@implementation FileDownloader

- (void)startDownloadWithURL:(NSURL *)url {
    // 模拟下载过程...
    NSLog(@"开始下载...");
    
    // 模拟进度更新 (调用可选方法前必须检查)
    if ([self.delegate respondsToSelector:@selector(downloadDidUpdateProgress:)]) {
        [self.delegate downloadDidUpdateProgress:0.5];
    }
    
    // 模拟下载成功 (调用必须方法)
    NSData *mockData = [@"下载的数据" dataUsingEncoding:NSUTF8StringEncoding];
    if (self.delegate) { // 检查代理是否存在
        [self.delegate downloadDidFinish:mockData];
    }
}

@end

如何使用它: MyViewController 创建一个 FileDownloader 实例,并将自己设置为其代理。

// 在 MyViewController.m 的某个方法中
FileDownloader *downloader = [[FileDownloader alloc] init];
downloader.delegate = self; // self 就是 MyViewController 实例
[downloader startDownloadWithURL:[NSURL URLWithString:@"http://example.com/file.zip"]];

优势FileDownloader 类可以被任何遵守 DownloaderDelegate 的对象复用,无论是 MyViewControllerAnotherController 还是一个专门的 DownloadManagerFileDownloader 完全与 MyViewController 解耦。

b. 数据源模式 (Data Source Pattern)

与委托模式类似,但关注点是提供数据。最经典的例子是 UITableView

  • UITableViewDelegate:负责处理用户交互和外观("应该做什么")。
  • UITableViewDataSource:负责提供表格需要显示的数据("数据是什么")。例如,有多少行(tableView:numberOfRowsInSection:)、每一行显示什么内容(tableView:cellForRowAtIndexPath:)。

UITableView 不知道也不关心数据来自哪里(数据库、网络、数组),它只向其 dataSource (数据源对象) 索要数据。

c. 实现多态

协议可以让你处理一组具有共同行为但继承体系完全不同的对象。

@protocol Runnable
- (void)run;
@end

@interface Person : NSObject <Runnable>
@end
@implementation Person
- (void)run { NSLog(@"人在跑"); }
@end

@interface Dog : NSObject <Runnable>
@end
@implementation Dog
- (void)run { NSLog(@"狗在跑"); }
@end

// 现在可以编写一个通用函数来处理任何能“跑”的对象
void makeItRun(id<Runnable> thing) {
    [thing run];
}

// 使用
Person *person = [[Person alloc] init];
Dog *dog = [[Dog alloc] init];

makeItRun(person); // 输出: 人在跑
makeItRun(dog);    // 输出: 狗在跑

// 或者放在一个数组里
NSArray<id<Runnable>> *runners = @[person, dog];
for (id<Runnable> runner in runners) {
    [runner run];
}

在这个例子中,PersonDog 没有共同的父类(除了NSObject),但因为它们都遵守 Runnable 协议,所以可以被同等对待。

4. 运行时检查

Objective-C 是动态语言,我们可以在运行时检查对象和协议的关系。

  • 检查对象是否遵守协议
    if ([someObject conformsToProtocol:@protocol(MyProtocolName)]) {
        // ...
    }
    
  • 检查对象是否能响应可选方法 (非常重要!)
    if ([self.delegate respondsToSelector:@selector(downloadDidUpdateProgress:)]) {
        [self.delegate downloadDidUpdateProgress:0.5];
    }
    
    如果不进行此检查就直接调用一个未被实现的可选方法,程序将会崩溃。

总结

特性描述
核心目的定义一套方法接口,实现类之间的解耦。
声明使用 @protocol ... @end
方法类型@required (必须实现,默认) 和 @optional (可选实现)。
属性可以声明属性,要求遵守者实现对应的存取方法。
继承协议可以继承自其他协议,聚合多个协议的要求。
类遵守在类的 @interface 中使用 <Protocol1, Protocol2>
类型用途id<MyProtocol> 作泛型指针,实现委托、数据源和多态。
安全使用 conformsToProtocol: 检查遵守情况,respondsToSelector: 安全调用可选方法。

协议是 Objective-C 面向对象设计中不可或缺的基石,掌握它对于编写可维护、可扩展和高内聚低耦合的 Cocoa/Cocoa Touch 应用至关重要。