Nếu là người mới bắt đầu làm quen với Cocos2d-x, chắc hẳn đa số đều cảm thấy bỡ ngỡ trước cách quản lý bộ nhớ của Cocos2d-x khi không thấy bất kỳ hàm new hay delete nào trong 1 game “thuần” Cocos2d-x.
Bài viết này sẽ giải đáp thắc mắc làm thế nào Cocos2d-x có thể cấp phát/thu hồi bộ nhớ của các đối tượng.
Reference Counting
Reference counting là 1 kỹ thuật để lưu trữ số lượng tham khảo, con trỏ đến 1 tài nguyên như các đối tượng, vùng nhớ hoặc các tài nguyên khác.
Ví dụ:
int main() { int *pi = new int; *pi = 10; int *pi_1 = pi; int *pi_2 = pi; return 0; }
Trong ví dụ trên, tài nguyên là 1 vùng nhớ dùng để lưu trữ số nguyên kiểu int
.
- Sau khi chương trình chạy dòng thứ 3, có 1 con trỏ “nắm giữ” tài nguyên.
- Sau khi chương trình chạy dòng thứ 5, 6 sẽ có lần lượt 2, 3 con trỏ “nắm giữ” tài nguyên. Reference counting là 1 kỹ thuật để lưu trữ những con số này.
Nhược điểm của cách quản lý bộ nhớ thông thường
Với cách quản lý bộ nhớ chỉ sử dụng 2 toán tử new
và delete
, rất khó để có thể biết được 1 tài nguyên còn được sử dụng hay không. Điều này sẽ gây khó khăn cho lập trình viên khi phải quyết định nên giải phóng hay giữ lại tài nguyên đã cấp phát. Ví dụ:
int main() { int *pi = new int; *pi = 10; int *pi_1 = pi; int *pi_2 = pi; delete pi_1; *pi = 11; *pi_2 = 12; return 0; }
Tại dòng 8, lập trình viên không muốn sử dụng pi_1
nữa nên đã giải phóng tài nguyên được quản lý bởi pi_1
. Điều đó cực kỳ nguyên hiểm vì ngoài pi_1
ra vẫn còn pi
và pi_2
“nắm giữ” và có nhu cầu sử dụng tài nguyên chung này. Việc sử dụng tài nguyên dùng chung không hợp lý có thể làm cho chương trình phải “dừng lại khi chưa được phép”.
CCAutoreleasePool
CCAutoreleasePool là 1 phương pháp để quản lý bộ nhớ trong Cocos2d-x dựa trên kỹ thuật Reference counting. Các tài nguyên trong pool có reference counting bằng 0 sẽ tự động được giải phóng lúc kết thúc mỗi lần lặp (message loop).
Retain
Khi gọi ptr->retain()
nghĩa là đã báo cho chương trình biết con trỏ ptr
nắm giữ tài nguyên nó đang trỏ đến. Reference counting sẽ tăng lên 1 đơn vị.
Release
Khi gọi ptr->release()
nghĩa là đã báo cho chương trình biết con trỏ ptr
không còn nắm giữ tài nguyên nó đang trỏ đến nữa. Reference counting sẽ giảm xuống 1 đơn vị.
Autorelease
Khi gọi object->autorelease()
đồng nghĩa với việc đưa đối tượng object vào CCAutoreleasePool. Tài nguyên được tham khảo bởi object sẽ được CCAutoreleasePool quản lý.
Ví dụ minh hoạ
bool StdioMemManLayer::init() { _pSprite1 = CCSprite::create("texture_1.png"); this->addChild(_pSprite1); _pSprite2 = CCSprite::create("texture_2.png"); } void StdioMemManLayer::logRotation() { CCLOG("_pSprite1 rotation: %.2f", _pSprite1->getRotation()); CCLOG("_pSprite2 rotation: %.2f", _pSprite2->getRotation()); }
Các đối tượng CCSprite trong Cocos2d-x sẽ tự động gọi phương thức autorelease
khi được tạo bởi phương thức create
. Điều đó có nghĩa là đối tượng này được quản lý bởi CCAutoreleasePool.
_pSprite1
sau khi khởi tạo được thêm vào layer StdioMemManLayer sẽ được layer này nắm giữ tài nguyên, vì vậy lệnh gọi hàm tại dòng 11 sẽ không xảy ra sự cố gì cả.
Khác với _pSprite1
, _pSprite2
sau khi khởi tạo đã không có “con trỏ” nào nắm giữ tài nguyên được trỏ tới bởi _pSprite2
nên lệnh gọi hàm tại dòng 12 sẽ gây crash chương trình vì CCAutoreleasePool đã giải phóng vùng nhớ _pSprite2
trỏ đến. Để đảm bảo câu lệnh này chạy đúng, lập trình viên phải gọi lệnh sau:
_pSprite2->retain();