Objective C-Categories là cách để chúng ta phân chia một lớp khởi tạo ra nhiều file khác nhau. Mục đích của việc này là để giảm bớt gánh nặng của việc duy trì các đoạn cơ sở code  lớn bằng việc modul hóa thành một lớp. Điều này giúp bạn tránh khỏi việc viết các tập tin với hơn 10000 dòng code mà không thể ứng dụng, di chuyển đến chỗ khác. Đồng thời, bạn cũng dễ dàng hơn trong việc phân nhóm, phân chia các phần trong một lớp để phục vụ các lập trình viên.

Học lập trình iOS bằng Objective-C

Using multiple files to implement a class

Trong phần này, chúng ta sẽ sử dụng một category để mở rộng một lớp mà không động chạm đến file gốc của lớp đó. Tiếp theo, chúng ta sẽ tìm hiểu cách chức năng này có thể được sử dụng để mô phỏng các phương thức protected. Extensions khá tương đồng với Categories, vì vậy chúng ta cũng sẽ nói qua một chút về chúng.

Setting Up

Trước khi chúng ta có thể thử nghiệm với Categories, chúng ta cần 1 class nền để có thể mở rộng từ nó. Tạo mới hoặc thay đổi giao diện lớp Car như sau:

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

@interface Car : NSObject

@property (copy) NSString *model;
@property (readonly) double odometer;

- (void)startEngine;
- (void)drive;
- (void)turnLeft;
- (void)turnRight;

@end

Đồng thời, hãy thay đổi phần thực thi tương ứng:

// Car.m
#import "Car.h"

@implementation Car

@synthesize model = _model;

- (void)startEngine {
    NSLog(@"Starting the %@'s engine", _model);
}
- (void)drive {
    NSLog(@"The %@ is now driving", _model);
}
- (void)turnLeft {
    NSLog(@"The %@ is turning left", _model);
}
- (void)turnRight {
    NSLog(@"The %@ is turning right", _model);
}

@end

Bây giờ, giả sử bạn muốn thêm một số phương thức liên quan khác để bảo trì phương tiện. Thay vì việc viết thêm các phương thức mới trong file Car.h và Car.m, ta có thể đặt chúng trong category

Creating Category

Giống như các lớp khác, category bao gồm một file giao diện và một file thực thi. Để thêm mới một category vào project của bạn, khởi tạo một file mới và chọn bản mẫu Objective C category trong danh mục iOS > Cocoa Touch. Đặt tên Maintenance cho trường Category, và Car cho trường Category on.

Học lập trình iOS online

Creating the Maintenance category

Chỉ có duy nhất một sự hạn chế trong việc đặt tên category là nó không được xung đột( trùng tên ) với các categories khác trong cùng một lớp. Qui ước đặt tên như sau: class name + categoryname.h (m). Ví dụ: Car+Maintenance.h, Car+Maintenance.m/

Bạn có thể thấy trong file Car+maintenance.h, giao diện của category cũng giống như giao diện của các lớp bình thường khác, ngoại trừ việc tên lớp sẽ được nối tiếp bởi tên category trong cặp ngoặc đơn. Bây giờ hãy thử thêm một vài phương thức mới vào category.

// Car+Maintenance.h
#import "Car.h"

@interface Car (Maintenance)

- (BOOL)needsOilChange;
- (void)changeOil;
- (void)rotateTires;
- (void)jumpBatteryUsingCar:(Car *)anotherCar;

@end

Trong quá trình chạy chương trình, các phương thức này sẽ trở thành một phần của lớp Car. Ngay cả khi nó được khai báo trong một file khác, và bạn có thể sử dụng, truy xuất chúng giống như khi bạn đặt chúng trong file Car.h.

Tất nhiên, bạn cũng sẽ phải viết phần thực thi cho category. Một lần nữa các bạn có thể thấy, phần thực thi của category cũng giống phần thực thi của các lớp bình thường khác, ngoại trừ việc tên category xuất hiện trong cặp ngoặc đơn ngay sau tên lớp.

// Car+Maintenance.m
#import "Car+Maintenance.h"

@implementation Car (Maintenance)

- (BOOL)needsOilChange {
    return YES;
}
- (void)changeOil {
    NSLog(@"Changing oil for the %@", [self model]);
}
- (void)rotateTires {
    NSLog(@"Rotating tires for the %@", [self model]);
}
- (void)jumpBatteryUsingCar:(Car *)anotherCar {
    NSLog(@"Jumped the %@ with a %@", [self model], [anotherCar model]);
}

@end

Có 1 điều quan trọng cần chú ý rằng, category cũng có thể được sử dụng để viết lại một phương thức đã có sẵn ở lớp nền( base class ), nhưng bạn không nên làm như vậy. Vấn đề là categories có cấu trúc cơ cấu phẳng. Nếu bạn viết lại một phương thức đã có sẵn, và rồi bạn lại muốn thay đổi một lần nữa bằng cách tạo tiếp một category khác, khi đó Objective C sẽ không thể nào phân biệt được phần thực thi nào sẽ được sử dụng. Kế thừa là cách tốt nhất trong trường hợp này.

Using category

Bất kì file nào để sử dụng một API được định nghĩa trong một category đều phải import giống như các lớp bình thường khác. Sau khi import Car+Maintenance.h tất cả các phương thức của nó đều có thể truy xuất trực tiếp từ lớp Car.

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Car+Maintenance.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *porsche = [[Car alloc] init];
        porsche.model = @"Porsche 911 Turbo";
        Car *ford = [[Car alloc] init];
        ford.model = @"Ford F-150";
        
        // "Standard" functionality from Car.h
        [porsche startEngine];
        [porsche drive];
        [porsche turnLeft];
        [porsche turnRight];
        
        // Additional methods from Car+Maintenance.h
        if ([porsche needsOilChange]) {
            [porsche changeOil];
        }
        [porsche rotateTires];
        [porsche jumpBatteryUsingCar:ford];
    }
    return 0;
}

Nếu như bạn xóa bỏ dòng lệnh Import, lớp Car sẽ trở về trạng thái ban đầu, và trình biên dịch sẽ thông báo các phương thức needsOilChange, changeOil…. Là không tồn tại.

Bài viết dịch từ Ry Tutorial