Sự khác biệt giữa Biến thành viên và Thuộc tính trong Objective-C

Trong Objective-C, việc sử dụng `@property` là một khái niệm cơ bản nhưng đôi khi gây nhầm lẫn, đặc biệt là về mối quan hệ giữa thuộc tính (property) và biến thành viên (member variable). Bài viết này sẽ làm rõ sự khác biệt này, từ cách tiếp cận hiện đại đến lịch sử phát triển của ngôn ngữ.

Tự động tổng hợp (Synthesize) trong Objective-C Hiện đại

Từ khi Apple chuyển sang sử dụng bộ biên dịch LLVM, việc khai báo thuộc tính (`@property`) đã trở nên đơn giản hơn rất nhiều. Khi bạn khai báo một thuộc tính trong tệp header (`.h`), trình biên dịch sẽ tự động:

  1. Tạo ra các phương thức getter và setter.
  2. Tạo ra một biến thể hiện (instance variable) tương ứng, thường được đặt tên với dấu gạch dưới ở đầu (ví dụ: `_userName`).

Bạn không cần phải viết thêm bất kỳ dòng mã nào trong tệp implementation (`.m`) để thực hiện việc này. Đây là cách làm tiêu chuẩn và được khuyến khích.

Ví dụ:

@interface UserModel : NSObject
@property (nonatomic, strong) NSString *userName;
@property (nonatomic, copy) NSString *email;
@end

@implementation UserModel
// Không cần @synthesize. Trình biên dịch đã tự động tạo ra:
// - Biến _userName
// - Biến _email
// - Phương thức userName và setEmail
@end

Lịch sử: Cách làm cũ với GCC Compiler

Trước khi LLVM trở thành bộ biên dịch mặc định, Objective-C yêu cầu bạn phải khai báo rõ ràng biến thành viên và sử dụng chỉ thị `@synthesize` để tạo ra các phương thức getter/setter.

Khi đó, một lớp thường có cấu trúc như sau: khai báo biến thành viên trong cặp ngoặc nhọn `{}`, sau đó khai báo thuộc tính, và cuối cùng là sử dụng `@synthesize` trong tệp implementation để liên kết thuộc tính với biến thành viên.

Ví dụ (Cách làm cũ):

@interface OldUserModel : NSObject
{
    // 1. Khai báo biến thành viên
    NSString *userName;
}
// 2. Khai báo thuộc tính
@property (nonatomic, copy) NSString *userName;
@end

@implementation OldUserModel
// 3. Liên kết thuộc tính với biến thành viên
@synthesize userName;
@end

Lưu ý rằng trong cách làm này, trình biên dịch sẽ không tự động tạo ra biến `_userName`. Nếu bạn không sử dụng `@synthesize`, bạn sẽ nhận được một cảnh báo về việc sử dụng biến tổng hợp mặc định.

Cú pháp dấu chấm (Dot Syntax): Phương thức hay Truy cập trực tiếp?

Một điểm gây nhầm lẫn phổ biến là cú pháp dấu chấm. Trong Objective-C, `object.property` không phải là cách để truy cập trực tiếp vào biến thành viên, giống như trong C++.

Thay vào đó, cú pháp dấu chấm là một cú pháp ngắn gọn (syntactic sugar) để gọi phương thức getter hoặc setter:

  • myObject.userName = @"John Doe"; tương đương với [myObject setUserName:@"John Doe"];
  • NSString *name = myObject.userName; tương đương với NSString *name = [myObject userName];

Điều này rất quan trọng vì nó đảm bảo rằng mọi truy cập vào dữ liệu đều đi qua các phương thức được kiểm soát, cho phép bạn thêm logic xác thực, ghi log, hoặc quản lý bộ nhớ trong các phương thức đó.

Các thuộc tính quan trọng của @property

Khi khai báo một thuộc tính, bạn có thể chỉ định các thuộc tính (attributes) để kiểm soát hành vi của nó:

  • nonatomic / atomic: `nonatomic` (không an toàn luồng) hiệu năng tốt hơn và thường được sử dụng trong môi trường đơn luồng. `atomic` (an toàn luồng) đảm bảo việc đọc/ghi là nguyên tử nhưng chậm hơn.
  • strong / weak / assign: `strong` giữ một tham chiếu mạnh đến đối tượng (tăng bộ đếm tham chiếu). `weak` giữ một tham chiếu yếu (không tăng bộ đếm). `assign` được dùng cho các kiểu dữ liệu nguyên thủy (int, float) hoặc đối tượng mà bạn không muốn giữ quyền sở hữu.
  • copy: Tạo một bản sao của đối tượng khi gán, thường được dùng cho các đối tượng `NSString`, `NSArray`, `NSDictionary` để đảm bảo tính bất biến.

Biến thành viên trong Category

Lưu ý rằng, trong một Category (phần mở rộng của lớp), bạn chỉ có thể thêm các phương thức, không thể thêm biến thành viên. Do đó, khi bạn khai báo một thuộc tính trong một Category, trình biên dịch sẽ chỉ tạo ra các phương thức getter/setter mà không tạo ra biến thể hiện nào cả. Bạn phải quản lý dữ liệu cho thuộc tính đó một cách thủ công.

Tóm tắt

  • Biến thành viên (Member Variable): Một biến được khai báo trong cặp ngoặc nhọn `{}` của interface. Nó là private và chỉ có thể được truy cập trực tiếp bên trong lớp.
  • Thuộc tính (Property): Một giao diện công khai để truy cập dữ liệu. Nó tự động tạo ra các phương thức getter/setter và, trong Objective-C hiện đại, một biến thể hiện ẩn (instance variable) với dấu gạch dưới.
  • Cú pháp dấu chấm: Là một cách viết tắt để gọi các phương thức getter/setter, không phải truy cập trực tiếp vào biến.

Thẻ: objective-c property instance-variable synthesize getter-setter

Đăng vào ngày 12 tháng 6 lúc 19:45