Biến và thuộc tính trong Objective-C

Biến (variable):

Cách đặt tên:

  • Tập ký tự: a-z, A-Z, 0-9, kí tự đặc biệt: _ @ # %
  • Tên biến được tạo bởi chữ cái và chữ số: Kí tự đầu tiên phải là chữ cái hoặc là dấu gạch dưới. ( _ )
  • Phân biệt chữ hoa và chữ thường, ví dụ: total, Total, TOTAL, Total là khác nhau.
  • Ví dụ về tên biến: count, result, _value, variable_1, etc.
  • Tên sai: 4th, order-no, error flag, etc...
  • Tốt hơn hết là đặt tên ngắn và có ý nghĩa.
  • Từ khóa – các từ dành sẵn với ý nghĩa xác định trước. Tên biến không được trùng với các từ khóa. Ví dụ: if, else, static, int char, while,.. Cú pháp khai báo:
      <Kiểu> tên_Biến ;

Ví dụ:

int number = 100 ;
NSString *string;

Phạm vi truy cập:

@private: Giới hạn phạm vi trong lớp mà biến được khai báo. • @protected: Giới hạn trong phạm vi lớp và lớp con kế thừa từ lớp mà biến đc khai báo. • @public: Không giới hạn phạm vi truy xuất. • • Mặc định khi không sử dụng từ khóa quyền truy xuất là @protected. Ta có thể định nghĩa phạm vi truy cập cho một nhóm các biến như sau:

 @private
 int att1;
 
 @public
NSString *str1;
 @protected
 float att1;
 double *str1;
 NSString *str2;
int att3;

Thuộc tính (Property)

Properties của một đối tượng cho phép các đối tượng khác có thế kiểm tra hoặc thay đổi giá trị các trạng thái của nó. Tuy nhiên trong một chương trình hướng đối tượng được thiết kế tốt sẽ không cho phép trực tiếp truy cập các trạng thái bên trong của một đối tượng. Thay vào đó các phương thức getter và setter được sử dụng như một khái niệm trừu tượng để truy cập đến dữ liệu bên cơ bản của đối tượng. Mục tiêu của từ khóa @property là giúp đối tượng có thể dễ dàng tạo và cấu hình các thuộc tính bằng cách tự động tạo ra các phương thức truy xuất (getter/setter). (Property cho phép đinh nghĩa các bộ truy xuất (setter/getter), mục đích của nó giúp cho việc truy xuất đến các biến một cách dễ dàng.)

Khai báo:

Trong file .h ta tiến hành khai báo:

@property (<attribute>) type propertyName;

Đầu tiên ta sẽ tìm hiểu xem những gì xảy ra khi sử dụng từ khóa @property. Ví dụ sau đây xây dựng 1 interface cho một class Car đơn giản, và phần implementation của nó:

// Car.h
#import <Foundation/Foundation.h> 
@interface Car : NSObject 
@property BOOL running;
@end

Trong file Car.m

// Car.m
#import "Car.h"
@implementation Car 
@synthesize running = _running; 
@end

Trình biên dịch tự động tạo ra các phương thức getter và setter cho property running. Objective-C quy ước tên mặc định như sau:

  • Tên của getter chính là tên của property.
  • Tên của setter được tạo bởi tên của property + tiền tố set.
  • Tên của biến thể hiện được tạo bởi tên của property + tiền tố là dấu gạch dưới. Giống như sau:
- (BOOL)running { 
- return _running;
}
- (void)setRunning:(BOOL)newValue {
_running = newValue; 
}

Sau khi khai báo một property với từ khóa @property, ta có thể gọi đến các phương thức get và set mà không cần phải xây dựng lại chúng trong class interface hay trong file implement của nó nữa.

Truy cập đến property:

Cách 1:

<Tên Object>.<Tên Property>;

Cách 2:

[<Tên Object> <tên phương thức getter/setter>]

Ví dụ:

myCar.running //Get myCar.runing = YES; //Set
//Hoặc cách tương tự:
[myCar runing] //Get [myCar setRunning: YES] //Set }

Để thay đổi cách hoạt động của các bộ truy xuất getter và setter vừa được tạo ra. Bạn có thể chỉ định các thuộc tính bên trong dấu ngoặc đơn sau từ khóa @property. Có thể cài đặt nhiều thuộc tính cho property bằng cách khai báo nhiều thuộc tính và phân cách chúng bởi dấu phẩy. Sau đây ta sẽ tìm hiểu về các thuộc tính sẵn có của Objective-C.

Các attribute:

1. getter = và setter =

Nếu không muốn sử dụng cách đặt tên mặc định cho các phương thức getter/setter của property, ta có thể thay đổi tên của các phương thức này bằng 2 thuộc tính getter=, setter=. Thuộc tính này thường được sử dụng khi ta khai báo một property kiểu Boolean, mà tiền tố của hàm getter được thay bằng is. Ví dụ: Ta thử thay đổi cách khai báo property trong ví dụ trước trong file Car.h như sau:

  @property (getter=isRunning) BOOL running;

Bộ truy xuất bây giờ sẽ là isRunning và setRunning. Lưu ý là property vẫn là running, và ta sẽ sử dụng như sau:

Car *honda = [[Car alloc] init];
honda.running = YES; // Tương đương với [honda setRunning:YES] 
NSLog(@"%d", honda.running); // Tương đương với [honda isRunning] 
NSLog(@"%d", [honda running]); // Lỗi: Phương thức không tồn tại

Đây là thuộc tính duy nhất có có đối số truyền vào là tên của phương thức truy xuất.

2. Readonly

Thuộc tính readonly dùng để tạo ra một property chỉ có thể đọc. Nó bỏ qua phương thức setter và ngăn chặn phép gán thông qua dấu chấm (.), nhưng phương thức getter không bị ảnh hưởng. Ví dụ: Ta sẽ thay đối interface đã xây dựng ở trên như sau: File Car.h

#import <Foundation/Foundation.h>
@interface Car : NSObject
@property (getter=isRunning, readonly) BOOL running;
- (void)startEngine; - (void)stopEngine;
@end

Thay vì để cho đối tượng trực tiếp thay đổi property, ta có thể tiến hành cài đặt từ bên trong (đối với biến thể hiện ứng với property ấy) thông qua phương thức startEngine và stopEngine, như sau:

// Car.m
#import "Car.h"
@implementation Car
- (void)startEngine { _running = YES;
}
- (void)stopEngine {
_running = NO; }
@end

Nhớ rằng từ khóa @property cũng sẽ tạo cho chúng ta một biến thể hiện, đó là lý do chúng ta có thể truy cập đến biến _running mà không cần phải khai báo ở bất cứ đâu (chúng ta không thể sử dụng self.running vì đây là property chỉ đọc - readonly). Kiểm tra lớp Car mới bằng cách thêm đoạn mã sau vào file main.m

Car *honda = [[Car alloc] init];
[honda startEngine];
NSLog(@"Running: %d", honda.running);
honda.running = NO; // Error: read-only property

Cho đến thời điểm này, property thực sự chỉ là các phím tắt thuận tiện cho phép chúng ta tránh phải viết các phương thức getter và setter. Tuy nhiên đối với các thuộc tính mà ta sẽ tìm hiểu sau đây sẽ làm thay đổi đáng kể các hành vi (behavior) của property. Và chúng cũng chỉ được áp dụng đối với những property mà lưu trữ một đối tượng của Objective-C (trái ngược với các kiểu dữ liệu nguyên thủy trong C).

3. nonatomic

atomic sẽ quy định các cách hoạt động của property trong một môi trường đa luồng. Nếu trong chương trình của bạn có nhiều hơn 1 thread, các phương thức getter và setter có thể cùng được gọi đến trong một thời điểm. Điều đó có nghĩa là các phương thức getter/setter trên có thể bị gián đoạn bởi một hoạt động khác, điều này dẫn đến dữ liệu có thể bị lỗi (hoặc không chính xác). Từ khóa atomic khóa các đối tượng lại để ngăn chặn điều này xảy ra, đảm bảo rằng các hoạt động get hoặc set đang làm việc trên một dữ liệu hoàn chỉnh (không bị chỉnh sửa bởi một tiến trình khác trong khi hoạt động set/get đang diễn ra). Tuy nhiên điều quan trọng phải hiểu rằng đây chỉ là một khía cạnh của các quy tắc an toàn khi sử dụng thread – mà bạn phải nắm vững. Sử dụng từ khóa atomic không có nghĩa là bạn sẽ có một chương trình đa luồng thực sự an toàn. Khi khai báo một property, thuộc tính mặc định của nó sẽ là atomic. Nếu chương trình của bạn không phải là một chương trình đa luồng (hoặc đang tiến hành một thread đảm bảo đầy đủ quy tắc an toàn), bạn có thể thay đổi thuộc tính này với từ khóa nonatomic, như sau:

@property (nonatomic) NSString *model;

4. strong

Thuộc tính strong tạo ra một mối quan hệ sở hữu đối với bất cứ đối tượng nào được gán cho property (tham chiếu mạnh – strong reference). Đây là thuộc tính mặc định khi khi khai báo một property, đảm bảo rằng giá trị vẫn tồn tại, chừng nào nó còn được gán cho property. Ta sẽ xem nó hoạt động như thế nào, trước tiên ta tạo ta một interface có tên là Person. Trong class này chỉ khai báo một property có tên là name.

// Person.h
#import <Foundation/Foundation.h> 
@interface Person : NSObject 
@property (nonatomic) NSString *name; 
@end

Phần implement sử dụng bộ truy xuất mặc định được tạo ra bởi property. Trong đó cũng override phương thức description của lớp NSObject, phương thức này trả về một chuỗi đại diện của đối tượng.

// Person.m
#import "Person.h"
@implementation Person
- (NSString *)description { 
return self.name;
}
@end

Tiếp theo ta thêm một property kiểu Person vào interface Car:

// Car.h
#import <Foundation/Foundation.h> #import "Person.h"
@interface Car : NSObject
@property (nonatomic) NSString *model;
@property (nonatomic, strong) Person *driver; 
@end

Trong hàm main.c

// main.m
#import <Foundation/Foundation.h> 
#import "Car.h"
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *john = [[Person alloc] init]; john.name = @"John";
Car *honda = [[Car alloc] init]; honda.model = @"Honda Civic"; honda.driver = john;
NSLog(@"%@ is driving the %@", honda.driver, honda.model); 
}
return 0; 
}

Vì driver là property kiểu strong, nên đối tượng honda có quyền sở hữu đối tượng john, điều này là cho giá trị của john vẫn tồn tại chừng nào đối tượng honda còn tham chiếu đến nó.

Chú ý:

  • Mặc định của con trỏ là strong.
  • Dùng strong pointer để giữ đối tượng mà mình tạo ra.
  • Không dùng strong pointer để giữ một đối tượng không thực sự phải của mình >> >> Không giải phóng được bộ nhớ. Ta sẽ dùng weak pointer:

5. Weak

Thường thì bạn sẽ nghĩ rằng nên sử dụng thuộc tính strong cho mỗi đối tượng. Tuy nhiên, tham chiếu mạnh (strong reference – sở hữu đối tượng, giống như strong property đã làm) sẽ gây ra một số vấn đề. Ta xét tiếp tục xét ví dụ về lớp Car và Person đã xây dựng ở trên. Giả sử chúng ta cần 1 tham chiếu từ người lái xe (driver) đến chiếc xe mà anh ta lái. Ta sẽ thêm một property vào interface Person:

// Person.h
#import <Foundation/Foundation.h>
@class Car;
@interface Person : NSObject
@property (nonatomic) NSString *name; 
@property (nonatomic, strong) Car *car;
@end

Chú ý: Dòng @class Car; dùng để khai báo trước lớp Car. Nó giống như ta nói với trình biên dịch rằng lớp Car có tồn tại, thế nên trình biên dịch không cần phải tìm ngay xem nó ở đâu. Chúng ta làm điều này thay vì sử dụng từ khóa #import như thông thường, bởi vì trong lớp Car cũng tiến hành #import “Person.h”, nếu làm như thế ta sẽ có vòng lặp vô tận import. (Trình biên không muốn có các vòng lặp vô tận). Sau đó ta thêm vào hàm main dòng sau:

john.car = honda;

Bây giờ thì ta có một mối quan hệ sở hữu từ Honda tới john, và mối mối quan hệ sở hữu khác từ john về honda. Điều đó có nghĩa là cả 2 đối tượng trên luôn luôn sở hữu lần nhau. Điều này làm cho trình quản lý bộ nhớ sẽ không thể hủy hai đối tượng này, ngay cả khi chúng không còn cần thiết. Đó gọi là một chu trình retain (retain cycle), là một hình thức rò rỉ bộ nhớ, không nên để điều này xảy ra khi lập trình. May mắn thay, rất dễ dàng để khắc phục vấn đề này, ta chỉ cần thay thế thuộc tính strong của property bằng thuộc tính weak – một tham chiếu yếu sẽ được tạo ra từ property này đến đối tượng khác. Trong Person.h, thay đổi khai báo property car như sau:

@property (nonatomic, weak) Car *car;

Thuộc tính weak tạo cho car một một liên kết không sở hữu. Nó cho phép đối tượng john có thể tham chiếu đến đối tượng honda mà vẫn tránh được một chu trình retain. Nhưng điều này cũng có nghĩa là rất có thể đối tượng honda sẽ bị xóa trong khi đối tượng john vẫn đang tham chiếu đến nó. Nếu điều đó xảy ra, thuộc tính weak sẽ dễ dàng đưa property car về nil để tránh một con trỏ vô định. Thuộc tính weak thường được sử dụng đối với cấu trúc dữ liệu kiểu cha-con. Quy ước, đối tượng cha cần duy trì một tham chiếu mạnh đến đối tượng con, trong khi đối tượng con có một tham chiếu yếu trở lại đối tượng cha. Tham chiếu yếu cũng là một phần quan trọng của delegate design pattern.

6. copy

Thuộc tính copy là một thay thế cho thuộc tính strong. Thay vì nắm quyền sở hữu các đối tượng hiện có, nó tạo ra một bản sao của bất cứ điều gì bạn gán cho property, sau đó có quyền sở hữu về bản sao này. Chỉ có các đối tượng phù hợp với giao thức NSCopying mới có thể sử dụng thuộc tính này. Các property dùng để thể hiện các giá trị rất phù hợp khi sử dụng từ khóa copy. Một ví dụ là lập trình viên thường copy một property kiểu NSString thay vì sử dụng một tham chiếu mạnh cho chúng.

// Car.h
@property (nonatomic, copy) NSString *model;

Bây giờ Car sẽ lưu trữ một giá trị thể hiện mới của bất cứ giá trị nào mà ta gán cho model. Nếu ta đang làm việc trên một giá trị có thể thay đổi (Mutable), khi ta gán giá trị này cho property, giá trị của property sẽ không thay đổi khi ta thay chỉ đổi giá trị của biến mutable. Ví dụ như sau:

// main.m
#import <Foundation/Foundation.h> 
#import "Car.h"
int main(int argc, const char * argv[]) { 
@autoreleasepool {
Car *honda = [[Car alloc] init];
NSMutableString *model = [NSMutableString stringWithString:@"Honda Civic"]; 
honda.model = model;
NSLog(@"%@", honda.model);
//Ta thử thay đổi giá trị của model
[model setString:@"Nissa Versa"]; 
NSLog(@"%@", honda.model);
return 0;
}

Trên đây ta đã tìm hiểu tất cả các thuộc tính có thể dùng trong một ứng dụng Objective-C hiện đại (IOS 5+). Nhưng có một số thuộc tính sau đây mà ta có thể gặp trong các thư viện cũ hoặc một số tài liệu.

7. retain

Thuộc tính retain giống như strong, được sử dụng trong phiên bản Manual Retain Release, nó có hiệu quả giống như nhau: Có quyền sở hữu đối với đối tượng đươc gán cho nó. Ta không nên sử dụng thuộc tính này trong môi trường Automatic Reference Counted.

8. unsafe_unretained

Property với thuộc tính unsafe_unretained có hoạt động tương thự thuộc tính weak, nhưng nó không tự đưa giá trị của property về nil khi giá trị mà nó tham chiếu đến bị giải phóng. Ta sử dụng thuộc tính này khi xây dựng một class không hỗ trợ thuộc tính weak.

9. assign

Thuộc tính assign không thực hiện bất kì một hình thức gọi quản lý bộ nhớ nào khi gán một giá trị mới cho property. Đây là trạng thái mặc định của các kiểu dữ liệu nguyên thủy, và nó là một cách để thực thi một tham chiếu yếu (weak reference) ở các phiên bản trước iOS 5. Giống như retain, ta không cần phải sử dụng trực tiếp nó trong các ứng dụng hiện đại.

Tóm tắt

Thường dùng:

getter= : Sử dụng tên tùy chỉnh cho phương thức getter. setter= : Sử dụng tên tùy chỉnh cho phương thức setter. -read-only: Không tích hợp phương thức setter. -automic: kiểm tra đảm bảo đồng bộ khi nhiều tiểu trình cùng truy cập trong môi trương đa luồng, là thuộc tính mặc định. -nonatomic: không đảm bảo tính đồng bộ dữ liệu của các bộ truy xuất trong môi trường đa luồng, vì thế nhanh hơn automic. -strong: tạo một mối quan hệ sở hữu giữa property với giá trị được gán. Đây là giá trị mặc đinh. -weak: Tạo một mối quan hệ không sở hữu giữa property với giá trị được gán. Sử dụng để tránh Retain Cycle (chu trình retain) -copy: Tạo một bản sao của giá trị được gán thay vì tham chiếu đến đến biến được gán, thường dùng khi là lớp NSString.

Ít dùng:

-assign: Dùng trong các phiên bản trước iOS 5, dùng cho các kiểu dữ liệu vô hướng, đây là thuộc tính mặc định. -retain: trong phiên bản Manual Retain Release, giống strong, tuy nhiên dùng, dùng cho kiểu dữ liệu là các lớp đối tượng. Không nên dùng trong Automatic Reference Counted. -unsafe_unretained: Giống weak, tuy nhiên ít dùng.