Protocol là một nhóm các thuộc tính và phương thức có liên quan, có thể được thực thi bởi bất kỳ class nào tuân thủ protocol đó. Nó linh hoạt hơn rất nhiều so với 1 lớp giao diện bình thường ( normal class interface ), vì nó có thể cho phép bạn sử dụng lại 1 lời khai báo hàm duy nhất trong các lớp không liên quan đến nó. Điều này giúp nó thể hiện quan hệ ngang hàng phía trên của 1 hệ thống kế thừa.

Unrelated classes adopting the StreetLegal protocol
Unrelated classes adopting the StreetLegal protocol

Đây là 1 modul tương đối ngắn bao gồm các vấn đề cơ bản phía sau khi làm việc với protocol. Chúng ta cũng sẽ thấy được nó phù hợp với hệ thống kiểu động của Objective C như thế nào.

Creating Protocols

Cũng giống như lớp giao diện ( class interface), thông thường protocols được đặt ở trong 1 file .h. Để thêm mới 1 protocol vào project của bạn, chọn File >  New > File… hoặc sử dụng tổ hợp phím tắt Cmd +N. Chọn “Objective C protocol” trong danh mục iOS > Cocoa Touch

Creating a protocol in Xcode
Creating a protocol in Xcode

Trong phần này, chúng ta sẽ làm việc với 1 protocol có tên là StreetLegal. Chọn Next, và lưu nó vào thư mục gốc của project.

Protocol của chúng ta sẽ có các hành vi cần thiết của 1 chiếc xe khi tham gia giao thông. Việc định nghĩa các hành vi riêng biệt trong 1 protocol giúp bạn áp dụng các hành vi ấy vào những đối tượng riêng biệt tùy ý, thay vì phải bắt  chúng kế thừa từ lớp cha của nó. 1 phiên bản đơn giản của StreetLegal protocol có cấu trúc như sau:

// StreetLegal.h
#import <Foundation/Foundation.h>

@protocol StreetLegal 

- (void)signalStop;
- (void)signalLeftTurn;
- (void)signalRightTurn;

@end

Bất cứ 1 lớp nào tuân thủ protocol này đều được đảm bảo để có thể thực thi tất cả các phương thức được khai báo ở trên. Từ khóa “” ngay phía sau tên của protocol để kết hợp NSObject Protocol. Điều đó có nghĩa là  bất cứ 1 đối tượng nào tuân thủ StreetLegal protocol thì cũng phải tuân thủ NSObject protocol.

Adopting Protocols

Các API ở trên có thể được tuân thủ ( áp dụng vào ) bởi 1 lớp bằng cách thêm nó trong cặp ngoặc “<>” ngay sau tên lớp, lớp cha. Khởi tạo 1 lớp mới có tên là Bicycle và thay đổi file.h giống như hình dưới. Chú ý là bạn phải có “#import [protocol name].h” thì bạn mới có thể dùng nó.

// Bicycle.h
#import <Foundation/Foundation.h>
#import "StreetLegal.h"

@interface Bicycle : NSObject 

- (void)startPedaling;
- (void)removeFrontWheel;
- (void)lockToStructure:(id)theStructure;

@end

Việc tuân thủ 1 protocol hiểu 1 cách đơn giản là lấy tất cả các phương thức trong StreetLegal.h để thêm vào Bicycle.h. Nó sẽ hoạt động 1 cách chính xác, ngay cả khi Bicycle kế thừa từ 1 lớp cha khác. 1 lớp có thể tuân thủ nhiều protocol cùng 1 lúc bằng cách thêm dấu phẩy để ngăn cách giữa các tên của protocol . Ví dụ <StreetLegal, SomeOtherProtocol>

Không có điều gì đặc biệt trong file Bicycle.m, chỉ đơn thuần là kiểm tra tất cả các phương thức được khai báo trong file Bicycle.h và StreetLegal.h  đã được triển khai.

// Bicycle.m
#import "Bicycle.h"

@implementation Bicycle

- (void)signalStop {
    NSLog(@"Bending left arm downwards");
}
- (void)signalLeftTurn {
    NSLog(@"Extending left arm outwards");
}
- (void)signalRightTurn {
    NSLog(@"Bending left arm upwards");
}
- (void)startPedaling {
    NSLog(@"Here we go!");
}
- (void)removeFrontWheel {
    NSLog(@"Front wheel is off."
          "Should probably replace that before pedaling...");
}
- (void)lockToStructure:(id)theStructure {
    NSLog(@"Locked to structure. Don't forget the combination!");
}

@end

Bây giờ khi bạn sử dụng lớp Bicycle, bạn có thể giả định nó đáp ứng lại các API được định nghĩa trong protocol. Hay nói cách khác, lớp Bicycle có thể sử dụng các phương thức được khai báo trong protocol StreetLegal

// main.m
#import <Foundation/Foundation.h>
#import "Bicycle.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Bicycle *bike = [[Bicycle alloc] init];
        [bike startPedaling];
        [bike signalLeftTurn];
        [bike signalStop];
        [bike lockToStructure:nil];
    }
    return 0;
}

Type Checking With Protocols

Giống như các lớp khác, protocol có thể được sử dụng như các biến kiểm tra. Để chắc chắn  1 lớp có tuân thủ 1 protocol cụ thể ko, ta đặt tên protocol ngay phía sau “Data Type”- kiểu dữ liệu trong lời khai báo biến, giống như đoạn code sau. Trong đoạn code này, giả định là lớp Car cũng tuân thủ StreetLegal protocol.

// main.m
#import <Foundation/Foundation.h>
#import "Bicycle.h"
#import "Car.h"
#import "StreetLegal.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        id  mysteryVehicle = [[Car alloc] init];
        [mysteryVehicle signalLeftTurn];
        
        mysteryVehicle = [[Bicycle alloc] init];
        [mysteryVehicle signalLeftTurn];
    }
    return 0;
}

Nó không quan trọng việc lớp Car và Bicycle có kế thừa từ cùng 1 lớp cha hay không. Thực tế là cả 2 lớp này đều tuân thủ StreetLegal protocol, nên ta có thể lưu giữ chúng trong 1 biến được khai báo với kiểu id. Đây là 1 ví dụ của việc protocol có thể ánh xạ được các chức năng đặc trưng giữa các lớp không liên quan.

1 đối tượng cũng có thể kiểm tra xem mình có tuân thủ 1 protocol nào đó không bằng cách sử dụng phương thức “ conformsToProtocol:”, phương thức này được định nghĩa trong NSObject protocol. Biến truyền vào của phương thức này là 1 đối tượng protocol. Nó hoạt động tương tự như @selector(), nhưng thay vì bạn truyền vào tên của phương thức thì ở đây ta sử dụng tên của protocol.

if ([mysteryVehicle conformsToProtocol:@protocol(StreetLegal)]) {
    [mysteryVehicle signalStop];
    [mysteryVehicle signalLeftTurn];
    [mysteryVehicle signalRightTurn];
}

Sử dụng protocol theo kiểu này, nó giống như việc chắc chắn rằng đối tượng này sẽ có tập hợp các hành vi đặc trưng ( Bicycle tuân thủ StreetLegal, nên chắc chắn rằng Bicycle sẽ có 3 hành vi đặc trưng của mọi loại phương tiện khi tham gia giao thông là signalStop, signalTurnLeft, signalTurnRight…). Đây là 1 công cụ rất mạnh, nó giúp bạn sử dụng 1 API đã được xác định rõ ràng mà không cần quan tâm đến kiểu của đối tượng mà bạn đang làm việc với.

Protocols In The Real World

1 trường hợp thực tế hơn mà bạn có thể thấy được trong các ứng dụng iOS. Đó là: điểm bắt đầu của bất kỳ ứng dụng nào cũng  là 1 đối tượng “ application delegate” để xử lý các sự kiện lớn trong vòng đời của chương trình. Thay vì việc bắt delegate phải kế thừa từ 1 lớp cha đặc biệt thì UKIT làm cho nó tuân thủ 1 protocol

@interface YourAppDelegate : UIResponder

Miễn là nó đáp ứng lại các phương thức được định nghĩa bởi UIApplicationDelegate, bạn có thể sử dụng bất kỳ đối tượng nào như application delegate. Việc sử dụng Delegate design pattern thông qua protocol thay vì kế thừa từ 1 lớp cha –(đoạn sau ko dịch đc L ) gives developer much more leeway when it comes to organizing their application

Summary

Qua phần này, chúng ta đã tìm hiểu thêm được 1 công cụ có tính tổ chức( kiến trúc). Protocol là cách để chia sẻ các thuộc tính trừu tượng và các phương thức vào 1 file chuyên biệt. Điều này giúp chúng ta giảm tối đa các đoạn code dư thừa, và cho phép bạn tự kiểm tra xem 1 đối tượng có hỗ trợ 1 tập hợp các hàm chuyên biệt nào đó hay ko. Bạn cũng sẽ tìm thấy được rất nhiều protocols trong Cocoa framework.

Tutorial from Ry