Quản lý bộ nhớ
Quản lý bộ nhớ là tiến trình xử lý việc khởi tạo vùng nhớ cho ứng dụng, sử dụng nó và giải
phóng nó khi ta đã hoàn thành công việc. Một chương trình quản lý bộ nhớ tốt sẽ sử dụng ít
vùng nhớ nhất có thể. Quản lý bộ nhớ là một vấn đề rất quan trọng trong lập trình. Một vấn
đề thường hay gặp phải đó là leak memory, khi chúng ta khởi tạo mà không giải phóng vùng
nhớ. Điều này có thể dẫn tới việc lãng phí bộ nhớ hoặc nghiêm trọng hơn là out of memory
và dẫn tới crash chương trình.
Objective-C cung cấp 3 cách quản lý bộ nhớ
• Manual retain-release (MRR) hoặc có thể gọi là Manual Reference Counting (MRC) có nghĩa
là chúng ta sẽ tự quản lý bộ nhớ bằng cách đánh dấu quá trình sử dụng của các object mà
ta có. Cụ thể là ta sẽ đếm việc khởi tạo, sử dụng và giải phóng object trong chương trình.
• Autiomatic Reference Counting (ARC), hệ thống sẽ dùng bộ đếm tương tự như MRR nhưng
nó sẽ thêm vào method quản lý bộ nhớ tự động tại thời điểm compile. Ta nên sử dụng ARC
cho các project mới
• Garbage Collection (GC), hệ thống sẽ tự động lưu vết và tự động giải phóng vùng nhớ của
object không cần thiết nữa. Nó sử dụng kỹ thuật quản lý bộ nhớ khác MRR hoặc ARC tuy
nhiên GC chỉ hỗ trợ Mac OS X mà không hỗ trợ iOS
Reference Counting
Khi một object được sinh ra, Objective-C sử dụng một kỹ thuật gọi là reference counting
hoặc đôi khi gọi là retain counting để theo vết vòng đời của object. Mỗi object sẽ có một số
interger đánh dấu nó, được biết như là reference count hoặc retain count. Khi một đoạn
code nào đó tác động tới object, đoạn code đó sẽ tăng retain count, khi kết thúc đoạn code
và không sử dụng object nữa, nó sẽ giảm retain count. Khi retain count về 0 cũng có nghĩa
là nó không sử dụng nữa và lúc này nó sẽ bị hủy khỏi bộ nhớ.
Khi object được khởi tạo bằng các từ khóa như alloc, new hoặc xử lý message copy, retain
count của object sẽ tự động là 1. Để tăng retain count lên, ta gửi retain message cho
object. Để giảm retain count ta gửi release message.
Khi retain count của object về 0 và object cần được hủy, Objective-C sẽ tự động gửi dealloc
message cho object. Ta có thể override dealloc method trong object để release resource nào
mà ta muốn. Không nên gọi dealloc method trực tiếp. Để có thể biết được retain count hiện
tại, ta gửi retainCount message. Tóm lại ta có 3 method quan trọng như sau
PHP Code:
PHP Code:
- (id) retain;
- (void) release;
- (unsigned) retainCount;
retain trả về id nên ta có thể kết hợp nó với chuỗi message ví dụ [[car retain] setTire:tire
atIndex:2]; báo cho object car tăng retain count và gửi message setTire. Ta coi ví dụ sau
PHP Code:
PHP Code:
@interface RetainTracker : NSObject
@end
@implementation RetainTracker
-(id)init
{
if (self = [super init])
{
NSLog(@”init”);
}
return self;
}
-(void)dealloc
{
NSLog(“dealloc”);
[super dealloc];
}
@end
Object RetainTracker trong ví dụ trên override lại dealloc methods để xử lý cần thiết khi
object bị hủy, tiếp theo ta sẽ coi vòng đời của object RetainTracker thông qua các methods
retain và alloc trong ví dụ dưới.
PHP Code:
PHP Code:
int main(int argc, const char *argv[])
{
RetainTracker *tracker = [RetainTracker new];
//count: 1
[tracker retain]; // count: 2
[tracker retain]; // count: 2
[tracker release]; // count: 1
[tracker retain]; // count: 2
[tracker release]; // count: 1
[tracker release]; // count: 0, dealloc
return (0);
}
Qua ví dụ trên, ta thấy rõ cơ cấu quản lý bộ nhớ của Objective-C như thế nào.
Object Ownership
Cho tới giờ việc quản lý bộ nhớ mà ta biết chưa có gì quá phức tạp, nó đi theo chu trình từ
lúc khởi tạo thông qua bộ đếm retain count và dealloc khi về 0. Vấn đề sẽ thực sự trở nên
rắc rối khi ta đề cập tới khái niệm object ownership.
Một object với instance variables trỏ tới một object khác được gọi là own object khác. Ta có
object CarParts có các variables engine trỏ tới object engine và tires trỏ tới các objects tires.
Ví dụ trong object CarParts có methods
PHP Code:
-(void)setEngine: (Engine*) newEngine;
Và trong hàm main() ta gọi
PHP Code:
PHP Code:
Engine *engine = [Engine new];
[car setEngine: engine];
Trong trường hợp trên ta sẽ gặp rắt rối nảy sinh khi object engine vừa nằm trong hàm
main() vừa được sử dụng trong object car (kiểu CarParts) và khó xác định khi nào thực sự
object engine bị dealloc. Nếu ta release trong hàm main() thì object engine sẽ bị xóa trong
khi CarParts vẫn đang sử dụng, tương tự nếu release trong CarParts object nhưng hàm
main() vẫn còn cần nó.
Cách giải quyết vấn đề trên là ta sẽ tăng retain count của engine lên 2 ngay khi object
CarParts sử dụng nó. Như vậy retain count của engine là 2 và khi ta release khi CarParts
không sử dụng nó nữa sau đó release tại main() thì retain count của engine về 0 sẽ được
dealloc một cách hợp lý.
Retain và Release trong Methods
Đầu tiên ta nói kỹ hơn một chút về object CarParts, nó có chứa một instance variable engine
kiểu Engine* và hàm setEngine được gọi là một accessor method (coi phần Property) có
chức năng gán object cho engine. Ngoài ra trong dealloc method của object CarParts có xử
lý cho phần dealloc của engine variables. Quay lại ví dụ ở phần trước, ta sẽ coi cách xử lý
trong method setEngine như thế nào:
PHP Code:
PHP Code:
-(void)setEngine: (Engine *)newEngine
{
engine=[newEngine retain]; //chú ý retain trả về id của Engine
}
Cách code như trên đã thõa đúng cách giải quyết mà ta đưa ra ban đầu, newEngine (tức là
engine mà main method đưa vào) được tăng retain count trong accessor method. Khi
dealloc method trong CarParts được gọi thì engine sẽ được release để retain count giảm
xuống như phương pháp ta đã nêu ở phía trên. Tuy nhiên cách code này có 1 vấn đề là nếu
engine variable đang trỏ tới một object Engine khác (tức là ta đã gửi message method
setEngine trước đó) thì object đó sẽ không được release khi engine trỏ sang object Engine
mới, điều này làm leak object Engine cũ, do đó cách code như trên là không tối ưu.
Để khắc phục tình trạng ở phía trên, ta xem thử đoạn code dưới đây
PHP Code:
PHP Code:
-(void)setEngine: (Engine *)newEngine
{
[engine release];
engine=[newEngine retain]; //chú ý retain trả về id của Engine
}
Cách code trên đưa ra giải pháp release ngay engine variable trước khi nhận object Engine
mới. Nhìn có vẻ hợp lý, tuy nhiên đoạn code trên sẽ gặp vấn đề cực kỳ lớn nếu như ta gặp
phải đoạn code dưới đây
PHP Code:
PHP Code:
Engine *engine = [Engine new]; //count: 1
Car *car1 = [Car new];
Car *car2 = [Car new];
[car1 setEngine: engine]; //count: 2
[engine release]; //count: 1
[car2 setEngine: [car1 engine]]; //count: 0
AutoRelease
AutoRelease là cơ chế mà ta không cần phải quan tâm đến việc release đối tương, nó sẽ tự
động giảm retain count xuống và release khi ta không sử dụng nó nữa. Để khai báo
autorelease ta sử dụng như sau :
PHP Code:
[PHP]Car *car1 = [[[Car alloc] init] autorelease];
[/PHP]
Tuy nhiên chúng ta nên cẩn thận đối với loại này. Ví dụ hàm ta có 1 đối tượng car1 trong
class và nó được khai báo như trên trong phương thức A(), ở phương thức B() ta sử dụng
đối tượng này sẽ bị crash do khi chạy hết hàm A() thì đối tượng car1 đã bị release.
Protocols
Khai báo các protocols methods có thể được implement trong bất cứ class nào. Protocols có
thể hữu ích trong ba trường hợp sau:
• Khai báo method mà class khác dự định sẽ thực thi
• Khai báo interface cho một object và che dấu nội dung bên trong nó
• Dễ dàng nắm bắt được cấu trúc nhiều class tương đồng có liên quan (khi sử dụng chung
interface)
Properties
Khai báo properties là một chức năng Objective-C cung cấp để ta có thể khai báo và truy
cập dễ dàng vào các giá trị của object thông qua các accessor methods.
Khái quát
Ta chủ yếu có thể truy cập vào properties của object thông qua cặp method truy cập
(accessor methods) được gọi là getter và setter. Sử dụng properties mang lại cho ta rất
nhiều thuận lợi:
• Cung cấp một cái nhìn rõ ràng cho người dùng, hiểu được cách thức tương tác với API.
• Compiler có thể tổng hợp các accessor methods (getter/setter) mà ta đã khai báo
Khai báo và thực thi Property
Trong phần này sẽ có 2 phần là khai báo và thực thi property
Khai báo Property
Một property được khai báo bắt đầu bằng từ khóa @property. @property có thể xuất hiện
bất cứ ở đâu trong danh sách khai báo các methods. @property cũng có xuất hiện trong
phần khai báo protocol và category.
PHP Code:
@property (attribute) type name;
Từ khóa @property khai báo một property. Giống như các dạng khác trong Objective-C, mỗi
property có một kiểu dữ liệu và một cái tên. Ngoài ra như ví dụ trên ta thấy còn có tùy chọn
các thuộc tính attribute thêm trong dấu ngoặc trong trường hợp đặc biệt, ta sẽ coi ở phần
bên dưới.
Tiếp theo chúng ta coi ví dụ đơn giản khai báo một property
PHP Code:
PHP Code:
@interface MyClass : NSObject
@property float value;
@end
Trong ví dụ trên thì dòng thứ 2 ta khai báo một property tên value kiểu float. Nó tương
đương với dòng lệnh
PHP Code:
PHP Code:
-(float)value;
-(void)setValue:(float)newValue;
Khai báo Property Attributes
Chúng ta có thể thêm vào một property các attributes theo cú pháp @property(attribute1,
attribute2,…). Nếu ta dùng @synthesize (trong phần implementation) để nói cho compiler
tạo các accessor methods truy cập property, compiler sẽ tự động xử lý hoàn toàn. Tuy nhiên
nếu ta tự xử lý accessor methods, ta phải chắc rằng nó phù hợp với những gì ta đã khai báo
trong interface, kể cả các attributes. Ta sẽ coi rõ trong phần implementation.
Accessor Method Names