MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 协议(Protocol)的应用与实践

2022-05-206.2k 阅读

一、Objective-C 协议基础概念

1.1 协议的定义

在 Objective-C 中,协议(Protocol)是一种特殊的接口形式,它定义了一组方法声明,但不包含这些方法的实现。协议的主要作用是允许不同类的对象遵循统一的行为规范,从而实现多态性。

协议使用 @protocol 关键字来定义,其基本语法如下:

@protocol ProtocolName <NSObject>
// 方法声明
- (void)method1;
@optional
- (void)optionalMethod;
@required
- (void)requiredMethod;
@end

在上述代码中,@protocol 后紧跟协议名称 ProtocolName,尖括号 <NSObject> 表示该协议继承自 NSObject 协议,NSObject 协议是 Objective-C 中所有类的基协议,它定义了一些基本的方法,如 initdeallocdescription 等。

协议中的方法可以分为两类:@required(默认)和 @optional@required 修饰的方法要求遵循该协议的类必须实现,而 @optional 修饰的方法则可选择性实现。

1.2 协议的遵循

一个类要遵循某个协议,需要在类的定义中通过尖括号列出该协议。例如:

@interface MyClass : NSObject <ProtocolName>
@end

此时 MyClass 类就声明遵循了 ProtocolName 协议,那么 MyClass 类必须实现 ProtocolName 协议中所有 @required 方法。

1.3 协议的继承

协议之间也可以继承,通过继承可以复用已有的协议声明。例如:

@protocol NewProtocol <ProtocolName>
- (void)newMethod;
@end

NewProtocol 协议继承自 ProtocolName 协议,遵循 NewProtocol 协议的类不仅要实现 NewProtocol 中定义的 newMethod 方法,还要实现 ProtocolName 协议中的所有 @required 方法。

二、协议在代理模式中的应用

2.1 代理模式简介

代理模式是一种常用的设计模式,在 Objective-C 中,协议常与代理模式结合使用。代理模式的核心思想是:一个对象(代理对象)代表另一个对象(委托对象)来处理某些任务。当委托对象需要执行特定操作时,它会将该操作委托给代理对象,代理对象则负责实际的处理。

2.2 基于协议实现代理模式的示例

假设我们有一个 ViewController 类,它需要在某个操作完成后通知另一个对象。我们可以通过协议和代理来实现这一功能。

首先,定义一个协议:

@protocol MyViewControllerDelegate <NSObject>
@required
- (void)viewControllerDidFinishTask:(UIViewController *)viewController;
@end

然后,在 ViewController 类中定义一个代理属性,并在适当的时候调用代理方法:

@interface MyViewController : UIViewController
@property (nonatomic, weak) id<MyViewControllerDelegate> delegate;
- (void)performTask {
    // 执行任务
    if ([self.delegate respondsToSelector:@selector(viewControllerDidFinishTask:)]) {
        [self.delegate viewControllerDidFinishTask:self];
    }
}
@end

最后,在另一个类中遵循该协议并设置为代理:

@interface AnotherClass : NSObject <MyViewControllerDelegate>
@end
@implementation AnotherClass
- (void)viewControllerDidFinishTask:(UIViewController *)viewController {
    NSLog(@"任务完成,来自 %@", viewController);
}
@end

// 在某个地方使用
AnotherClass *another = [[AnotherClass alloc] init];
MyViewController *vc = [[MyViewController alloc] init];
vc.delegate = another;
[vc performTask];

在上述代码中,MyViewController 类将任务完成的通知委托给了 AnotherClass 对象,AnotherClass 通过实现协议方法来处理该通知。

2.3 代理模式中协议的优势

  1. 解耦委托对象和代理对象:委托对象不需要知道代理对象的具体类,只需要知道代理对象遵循特定的协议,这使得代码的可维护性和可扩展性大大提高。
  2. 提高代码复用性:不同的类可以遵循相同的协议并作为代理,从而实现不同的处理逻辑,而委托对象的代码无需改变。

三、协议在数据传递中的应用

3.1 跨视图控制器数据传递

在 iOS 开发中,经常会遇到跨视图控制器传递数据的情况。协议是一种有效的实现方式。

例如,我们有一个 DetailViewController,它需要将用户输入的数据传递回 MainViewController

首先,在 DetailViewController 中定义协议和代理属性:

@protocol DetailViewControllerDelegate <NSObject>
- (void)detailViewController:(DetailViewController *)detailVC didFinishWithData:(NSString *)data;
@end

@interface DetailViewController : UIViewController
@property (nonatomic, weak) id<DetailViewControllerDelegate> delegate;
@end

DetailViewController 的保存数据操作中调用代理方法:

@implementation DetailViewController
- (IBAction)saveButtonTapped:(id)sender {
    NSString *data = @"用户输入的数据";
    if ([self.delegate respondsToSelector:@selector(detailViewController:didFinishWithData:)]) {
        [self.delegate detailViewController:self didFinishWithData:data];
    }
    [self.navigationController popViewControllerAnimated:YES];
}
@end

MainViewController 中遵循协议并设置为代理:

@interface MainViewController : UIViewController <DetailViewControllerDelegate>
@end

@implementation MainViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"showDetail"]) {
        DetailViewController *detailVC = segue.destinationViewController;
        detailVC.delegate = self;
    }
}

- (void)detailViewController:(DetailViewController *)detailVC didFinishWithData:(NSString *)data {
    NSLog(@"接收到的数据: %@", data);
}
@end

通过这种方式,DetailViewController 可以将数据传递回 MainViewController

3.2 多视图控制器间数据传递

在复杂的应用中,可能涉及多个视图控制器之间的数据传递。协议同样可以很好地应对这种情况。

假设我们有 ViewControllerAViewControllerBViewControllerCViewControllerA 启动 ViewControllerBViewControllerB 启动 ViewControllerC,而 ViewControllerC 需要将数据传递回 ViewControllerA

我们可以在 ViewControllerB 中定义一个协议,ViewControllerA 遵循该协议并设置为 ViewControllerB 的代理。ViewControllerB 再将 ViewControllerA 的代理关系传递给 ViewControllerC

ViewControllerB 的协议和代理设置:

@protocol ViewControllerBProtocol <NSObject>
- (void)viewControllerCDidFinish:(NSString *)data;
@end

@interface ViewControllerB : UIViewController
@property (nonatomic, weak) id<ViewControllerBProtocol> delegate;
@end

ViewControllerB 启动 ViewControllerC 并传递代理:

@implementation ViewControllerB
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"showC"]) {
        ViewControllerC *vcC = segue.destinationViewController;
        vcC.delegate = self.delegate;
    }
}
@end

ViewControllerC 调用代理方法传递数据:

@protocol ViewControllerCProtocol <NSObject>
- (void)viewControllerCDidFinish:(NSString *)data;
@end

@interface ViewControllerC : UIViewController
@property (nonatomic, weak) id<ViewControllerCProtocol> delegate;
@end

@implementation ViewControllerC
- (IBAction)saveDataInC:(id)sender {
    NSString *data = @"来自ViewControllerC的数据";
    if ([self.delegate respondsToSelector:@selector(viewControllerCDidFinish:)]) {
        [self.delegate viewControllerCDidFinish:data];
    }
    [self.navigationController popViewControllerAnimated:YES];
}
@end

ViewControllerA 遵循协议接收数据:

@interface ViewControllerA : UIViewController <ViewControllerBProtocol>
@end

@implementation ViewControllerA
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"showB"]) {
        ViewControllerB *vcB = segue.destinationViewController;
        vcB.delegate = self;
    }
}

- (void)viewControllerCDidFinish:(NSString *)data {
    NSLog(@"ViewControllerA 接收到数据: %@", data);
}
@end

通过这种链式代理和协议的方式,实现了多个视图控制器间的数据传递。

四、协议在框架设计中的应用

4.1 自定义框架中的协议设计

在开发自定义框架时,协议可以用于定义框架对外提供的接口。例如,我们开发一个网络请求框架,我们可以定义一个协议来处理网络请求的结果。

首先,定义协议:

@protocol NetworkRequestDelegate <NSObject>
@required
- (void)networkRequest:(NSURLRequest *)request didFinishWithResponse:(NSHTTPURLResponse *)response data:(NSData *)data;
@optional
- (void)networkRequest:(NSURLRequest *)request didFailWithError:(NSError *)error;
@end

然后,在网络请求类中使用该协议:

@interface NetworkManager : NSObject
@property (nonatomic, weak) id<NetworkRequestDelegate> delegate;
- (void)sendRequest:(NSURLRequest *)request;
@end

@implementation NetworkManager
- (void)sendRequest:(NSURLRequest *)request {
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            if ([self.delegate respondsToSelector:@selector(networkRequest:didFailWithError:)]) {
                [self.delegate networkRequest:request didFailWithError:error];
            }
        } else {
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            if ([self.delegate respondsToSelector:@selector(networkRequest:didFinishWithResponse:data:)]) {
                [self.delegate networkRequest:request didFinishWithResponse:httpResponse data:data];
            }
        }
    }];
    [task resume];
}
@end

在使用框架的项目中,某个类遵循该协议并处理网络请求结果:

@interface MyAppClass : NSObject <NetworkRequestDelegate>
@end

@implementation MyAppClass
- (void)networkRequest:(NSURLRequest *)request didFinishWithResponse:(NSHTTPURLResponse *)response data:(NSData *)data {
    // 处理成功的响应
    NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"成功响应: %@", responseString);
}

- (void)networkRequest:(NSURLRequest *)request didFailWithError:(NSError *)error {
    // 处理失败的错误
    NSLog(@"请求失败: %@", error);
}
@end

// 使用
MyAppClass *appClass = [[MyAppClass alloc] init];
NetworkManager *manager = [[NetworkManager alloc] init];
manager.delegate = appClass;
NSURL *url = [NSURL URLWithString:@"https://example.com/api"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[manager sendRequest:request];

通过这种方式,框架使用者可以通过遵循协议来定制网络请求的处理逻辑,而框架本身只负责发起请求和调用协议方法。

4.2 与系统框架结合使用协议

Objective-C 中的许多系统框架都使用了协议。例如,UITableView 使用 UITableViewDataSourceUITableViewDelegate 协议来提供数据和处理用户交互。

UITableViewDataSource 协议定义了一些方法,用于告诉 UITableView 有多少行数据、每行显示什么内容等。例如:

@interface MyTableViewController : UIViewController <UITableViewDataSource>
@property (nonatomic, strong) NSArray *dataArray;
@end

@implementation MyTableViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    cell.textLabel.text = self.dataArray[indexPath.row];
    return cell;
}
@end

UITableViewDelegate 协议则定义了一些处理用户交互的方法,如点击某行时的操作:

@interface MyTableViewController : UIViewController <UITableViewDelegate>
@end

@implementation MyTableViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"点击了第 %ld 行", (long)indexPath.row);
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end

通过遵循这些系统框架提供的协议,开发者可以方便地定制视图的行为和数据展示。

五、协议的高级应用

5.1 协议作为类型

在 Objective-C 中,协议可以作为一种类型来使用。例如,我们可以定义一个属性或者方法参数的类型为协议类型。

@interface MyClass : NSObject
@property (nonatomic, strong) id<MyProtocol> object;
- (void)doSomethingWithObject:(id<MyProtocol>)obj;
@end

这样,object 属性和 doSomethingWithObject: 方法可以接受任何遵循 MyProtocol 协议的对象,而不需要关心对象的具体类。

5.2 协议的组合

有时候,一个类可能需要遵循多个协议,以实现复杂的功能。例如:

@interface ComplexClass : NSObject <Protocol1, Protocol2, Protocol3>
@end

ComplexClass 类需要实现 Protocol1Protocol2Protocol3 协议中的所有 @required 方法。这种协议的组合可以让类具备多种不同的行为,同时也增加了代码的灵活性和可扩展性。

5.3 协议与分类(Category)结合

分类(Category)可以为已有的类添加方法,而协议可以定义这些方法的规范。例如,我们有一个 NSString 类,我们可以通过分类为其添加遵循特定协议的方法。

首先,定义协议:

@protocol StringFormattingProtocol <NSObject>
- (NSString *)formattedString;
@end

然后,为 NSString 类定义分类并实现协议方法:

@interface NSString (Formatting) <StringFormattingProtocol>
@end

@implementation NSString (Formatting)
- (NSString *)formattedString {
    return [self stringByAppendingString:@" (Formatted)"];
}
@end

这样,所有的 NSString 对象都可以调用 formattedString 方法,并且遵循了 StringFormattingProtocol 协议。

六、协议使用中的注意事项

6.1 协议方法的实现检查

在开发过程中,要确保遵循协议的类正确实现了所有 @required 方法。虽然编译器会在编译时检查部分错误,但对于动态类型的对象,可能在运行时才会发现未实现协议方法的问题。可以通过在运行时使用 respondsToSelector: 方法来检查对象是否实现了特定的协议方法,以避免程序崩溃。

6.2 避免协议滥用

虽然协议提供了很大的灵活性,但过度使用协议可能会导致代码变得复杂和难以维护。在设计协议时,要确保协议的定义清晰、简洁,并且具有明确的用途。避免定义过于宽泛或者功能重复的协议。

6.3 协议与内存管理

当使用协议定义代理属性时,通常将代理属性声明为 weak 类型,以避免循环引用导致的内存泄漏。例如:

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

这样,当代理对象和拥有代理属性的对象之间不存在其他强引用循环时,它们可以正常释放内存。

在实际开发中,深入理解和合理应用 Objective-C 协议,可以极大地提高代码的质量、可维护性和可扩展性,使我们能够开发出更加健壮和灵活的应用程序。无论是在小型项目还是大型框架的开发中,协议都发挥着重要的作用。