Pertahankan Penggunaan Proyek Flash Memori Anda Stabil Dengan Pooling Objek
Indonesian (Bahasa Indonesia) translation by Suci Rohini (you can also view the original English article)
Penggunaan memori adalah aspek pengembangan yang Anda harus benar-benar berhati-hati, atau mungkin memperlambat aplikasi Anda, mengambil banyak memori atau bahkan menabrak segalanya. Tutorial ini akan membantu Anda menghindari kemungkinan hasil buruk itu!
Pratinjau Hasil Akhir
Mari kita lihat hasil akhir yang akan kita upayakan:
Klik di mana saja di atas panggung untuk membuat efek kembang api, dan awasi profiler memori di sudut kiri atas.
Langkah 1: Pendahuluan
Jika Anda pernah membuat profil aplikasi Anda menggunakan profiling tool apa pun atau menggunakan kode atau pustaka apa pun yang memberi tahu Anda penggunaan memori saat ini dari aplikasi Anda, Anda mungkin telah memperhatikan bahwa berkali-kali penggunaan memori naik, dan kemudian turun lagi (jika Anda memerlukan, kode Anda luar biasa!). Yah, meskipun lonjakan ini disebabkan oleh penggunaan memori besar terlihat agak keren, itu bukan kabar baik untuk aplikasi Anda atau (akibatnya) pengguna Anda. Teruslah membaca untuk memahami mengapa ini terjadi dan bagaimana menghindarinya.
Langkah 2: Penggunaan Baik dan Buruk
Gambar di bawah ini adalah contoh manajemen memori yang buruk. Itu dari prototipe game. Anda harus memperhatikan dua hal penting: lonjakan besar pada penggunaan memori dan puncak penggunaan memori. Puncaknya hampir di 540Mb! Itu berarti prototipe ini sendiri mencapai titik menggunakan 540Mb RAM komputer pengguna - dan itu adalah sesuatu yang Anda pasti ingin hindari.



Masalah ini dimulai ketika Anda mulai membuat banyak instance objek dalam aplikasi Anda. Mesin virtual yang tidak digunakan akan terus menggunakan memori aplikasi Anda sampai pengumpul sampah berjalan, ketika mereka tidak dapat dialokasikan kembali - menyebabkan lonjakan besar. Situasi yang lebih buruk terjadi ketika instans tidak akan dialokasikan, menyebabkan penggunaan memori aplikasi Anda terus tumbuh sampai sesuatu crash atau rusak. Jika Anda ingin tahu lebih banyak tentang masalah yang terakhir dan bagaimana cara menghindarinya, baca Tip Cepat ini tentang pengumpulan sampah.
Dalam tutorial ini kita tidak akan membahas masalah pengumpul sampah. Kita malah akan bekerja pada membangun struktur yang secara efisien menyimpan objek dalam memori, menjadikan penggunaannya benar-benar stabil dan dengan demikian menjaga pengumpul sampah dari membersihkan memori, membuat aplikasi lebih cepat. Lihatlah penggunaan memori dari prototipe yang sama di atas, tetapi kali ini dioptimalkan dengan teknik yang ditunjukkan di sini:



Semua peningkatan ini dapat dicapai dengan menggunakan pengumpulan objek. Baca terus untuk memahami apa itu dan bagaimana cara kerjanya.
Langkah 3: Jenis Pools
Pengumpulan objek adalah teknik di mana sejumlah objek yang telah ditentukan dibuat ketika aplikasi diinisialisasi, dan disimpan dalam memori selama seluruh masa aplikasi. Kumpulan objek memberikan objek saat aplikasi memintanya, dan mengatur ulang objek kembali ke kondisi awal saat aplikasi selesai menggunakannya. Ada banyak jenis kumpulan objek, tetapi kita hanya akan melihat dua di antaranya: kumpulan objek statis dan dinamis.
Kelompok objek statis menciptakan jumlah objek yang ditentukan dan hanya menyimpan jumlah objek selama waktu hidup aplikasi keseluruhan. Jika suatu objek diminta tetapi pool telah memberikan semua objeknya, pool mengembalikan nol. Saat menggunakan jenis pool ini, perlu untuk mengatasi masalah seperti meminta objek dan tidak mendapatkan apa-apa.
Pool objek dinamis juga membuat sejumlah objek pada inisialisasi, tetapi ketika sebuah objek diminta dan pool kosong, pool membuat instance lain secara otomatis dan mengembalikan objek itu, meningkatkan ukuran pool dan menambahkan objek baru ke dalamnya.
Dalam tutorial ini kita akan membangun aplikasi sederhana yang menghasilkan partikel ketika pengguna mengklik layar. Partikel-partikel ini akan memiliki masa hidup yang terbatas, dan kemudian akan dihapus dari layar dan kembali ke kolam. Untuk melakukan itu, pertama-tama kita akan membuat aplikasi ini tanpa pengumpulan objek dan memeriksa penggunaan memori, dan kemudian mengimplementasikan kumpulan objek dan membandingkan penggunaan memori sebelumnya.
Langkah 4: Aplikasi Awal
Buka FlashDevelop (lihat panduan ini) dan buat proyek AS3 baru. Kita akan menggunakan kotak kecil berwarna sederhana sebagai gambar partikel, yang akan digambar dengan kode dan akan bergerak sesuai dengan sudut acak. Buat kelas baru yang disebut Partikel yang memperluas Sprite. Saya akan berasumsi bahwa Anda dapat menangani pembuatan partikel, dan hanya menyoroti aspek-aspek yang akan melacak masa hidup partikel dan penghapusan dari layar. Anda dapat mengambil kode sumber lengkap tutorial ini di bagian atas halaman jika Anda kesulitan membuat partikel.
1 |
private var _lifeTime:int; |
2 |
|
3 |
public function update(timePassed:uint):void |
4 |
{
|
5 |
// Making the particle move
|
6 |
x += Math.cos(_angle) * _speed * timePassed / 1000; |
7 |
y += Math.sin(_angle) * _speed * timePassed / 1000; |
8 |
|
9 |
// Small easing to make movement look pretty
|
10 |
_speed -= 120 * timePassed / 1000; |
11 |
|
12 |
// Taking care of lifetime and removal
|
13 |
_lifeTime -= timePassed; |
14 |
|
15 |
if (_lifeTime <= 0) |
16 |
{
|
17 |
parent.removeChild(this); |
18 |
}
|
19 |
}
|
Kode di atas adalah kode yang bertanggung jawab atas penghapusan partikel dari layar. Kita membuat variabel yang disebut _lifeTime
untuk memuat jumlah milidetik yang akan ditampilkan oleh partikel di layar. Kita menginisialisasi secara default nilainya menjadi 1000 pada konstruktor. Fungsi update()
disebut setiap bingkai dan menerima jumlah milidetik yang melewati antar bingkai, sehingga dapat mengurangi nilai masa hidup partikel. Ketika nilai ini mencapai 0 atau kurang, partikel secara otomatis meminta induknya untuk menghapusnya dari layar. Sisa kode menangani pergerakan partikel.
Sekarang kita akan membuat banyak dari ini dibuat ketika klik mouse terdeteksi. Pergi ke Main.as:
1 |
private var _oldTime:uint; |
2 |
private var _elapsed:uint; |
3 |
|
4 |
private function init(e:Event = null):void |
5 |
{
|
6 |
removeEventListener(Event.ADDED_TO_STAGE, init); |
7 |
// entry point
|
8 |
stage.addEventListener(MouseEvent.CLICK, createParticles); |
9 |
addEventListener(Event.ENTER_FRAME, updateParticles); |
10 |
|
11 |
_oldTime = getTimer(); |
12 |
}
|
13 |
|
14 |
private function updateParticles(e:Event):void |
15 |
{
|
16 |
_elapsed = getTimer() - _oldTime; |
17 |
_oldTime += _elapsed; |
18 |
|
19 |
for (var i:int = 0; i < numChildren; i++) |
20 |
{
|
21 |
if (getChildAt(i) is Particle) |
22 |
{
|
23 |
Particle(getChildAt(i)).update(_elapsed); |
24 |
}
|
25 |
}
|
26 |
}
|
27 |
|
28 |
private function createParticles(e:MouseEvent):void |
29 |
{
|
30 |
for (var i:int = 0; i < 10; i++) |
31 |
{
|
32 |
addChild(new Particle(stage.mouseX, stage.mouseY)); |
33 |
}
|
34 |
}
|
Kode untuk memperbarui partikel harus familier bagi Anda: itu adalah akar dari loop berbasis waktu yang sederhana, yang biasa digunakan dalam gim. Jangan lupa pernyataan impor:
1 |
import flash.events.Event; |
2 |
import flash.events.MouseEvent; |
3 |
import flash.utils.getTimer; |
Anda sekarang dapat menguji aplikasi Anda dan profilnya menggunakan profiler bawaan FlashDevelop. Klik beberapa kali di layar. Seperti apa rupa penggunaan memori saya:



Saya mengklik sampai pengumpul sampah mulai berjalan. Aplikasi ini menciptakan lebih dari 2000 partikel yang dikumpulkan. Apakah ini mulai terlihat seperti penggunaan memori prototipe itu? Sepertinya, dan ini jelas tidak baik. Untuk mempermudah pembuatan profil, kita akan menambahkan utilitas yang disebutkan di langkah pertama. Berikut kode yang akan ditambahkan di Main.as:
1 |
private function init(e:Event = null):void |
2 |
{
|
3 |
removeEventListener(Event.ADDED_TO_STAGE, init); |
4 |
// entry point
|
5 |
stage.addEventListener(MouseEvent.CLICK, createParticles); |
6 |
addEventListener(Event.ENTER_FRAME, updateParticles); |
7 |
|
8 |
addChild(new Stats()); |
9 |
|
10 |
_oldTime = getTimer(); |
11 |
}
|
Jangan lupa untuk mengimpor net.hires.debug.Stats
dan siap digunakan!
Langkah 5: Mendefinisikan Objek Poolable
Aplikasi yang dibangun pada Langkah 4 cukup sederhana. Ini hanya menampilkan efek partikel sederhana, tetapi menciptakan banyak masalah dalam memori. Pada langkah ini, kita akan mulai mengerjakan kumpulan objek untuk memperbaiki masalah itu.
Langkah pertama kita menuju solusi yang baik adalah memikirkan bagaimana objek dapat dikumpulkan tanpa masalah. Di kumpulan objek, kita harus selalu memastikan bahwa objek yang dibuat siap digunakan dan bahwa objek yang dikembalikan benar-benar "terisolasi" dari sisa aplikasi (mis. Tidak memiliki referensi untuk hal-hal lain). Untuk memaksa setiap objek yang dikumpulkan untuk dapat melakukan itu, kita akan membuat interface. Interface ini akan menetapkan dua fungsi penting yang harus dimiliki objek: renew()
dan destroy()
. Dengan begitu, kita selalu dapat memanggil metode-metode itu tanpa khawatir tentang apakah objek memilikinya (atau tidak). Ini juga berarti bahwa setiap objek yang ingin kita kumpulkan perlu mengimplementasikan interface ini. Jadi begini:
1 |
package
|
2 |
{
|
3 |
public interface IPoolable |
4 |
{
|
5 |
function get destroyed():Boolean; |
6 |
|
7 |
function renew():void; |
8 |
function destroy():void; |
9 |
}
|
10 |
}
|
Karena partikel kita dapat dikumpulkan, kita perlu membuatnya mengimplementasikan IPoolable
. Pada dasarnya kita memindahkan semua kode dari konstruktornya ke fungsi renew()
, dan menghilangkan referensi eksternal ke objek di fungsi destroy()
. Begini tampilannya:
1 |
/* INTERFACE IPoolable */
|
2 |
|
3 |
public function get destroyed():Boolean |
4 |
{
|
5 |
return _destroyed; |
6 |
}
|
7 |
|
8 |
public function renew():void |
9 |
{
|
10 |
if (!_destroyed) |
11 |
{
|
12 |
return; |
13 |
}
|
14 |
|
15 |
_destroyed = false; |
16 |
|
17 |
graphics.beginFill(uint(Math.random() * 0xFFFFFF), 0.5 + (Math.random() * 0.5)); |
18 |
graphics.drawRect( -1.5, -1.5, 3, 3); |
19 |
graphics.endFill(); |
20 |
|
21 |
_angle = Math.random() * Math.PI * 2; |
22 |
|
23 |
_speed = 150; // Pixels per second |
24 |
|
25 |
_lifeTime = 1000; // Miliseconds |
26 |
}
|
27 |
|
28 |
public function destroy():void |
29 |
{
|
30 |
if (_destroyed) |
31 |
{
|
32 |
return; |
33 |
}
|
34 |
|
35 |
_destroyed = true; |
36 |
|
37 |
graphics.clear(); |
38 |
}
|
Konstruktor juga tidak perlu meminta argumen lagi. Jika Anda ingin meneruskan informasi apa pun ke objek, Anda harus melakukannya melalui fungsi sekarang. Karena cara fungsi renew()
berfungsi sekarang, kita juga perlu menetapkan _destroyed
menjadi true
dalam konstruktor sehingga fungsi tersebut dapat dijalankan.
Dengan itu, kita baru saja mengadaptasi kelas Partikel
kita untuk berperilaku sebagai IPoolable
. Dengan begitu, objek pool akan mampu membuat kumpulan partikel.
Langkah 6: Memulai Pool Objek
Sekarang saatnya untuk membuat kumpulan objek fleksibel yang dapat menyatukan objek apa pun yang kita inginkan. Kelompok ini akan bertindak sedikit seperti pabrik: alih-alih menggunakan kata kunci new
untuk membuat objek yang dapat Anda gunakan, kita akan memanggil metode di kelompok yang mengembalikan kepada objek.
Untuk tujuan kesederhanaan, objek pool akan menjadi Singleton. Dengan begitu kita dapat mengaksesnya di mana saja dalam kode kita. Mulailah dengan membuat kelas baru yang disebut "ObjectPool" dan menambahkan kode untuk menjadikannya Singleton:
1 |
package
|
2 |
{
|
3 |
public class ObjectPool |
4 |
{
|
5 |
private static var _instance:ObjectPool; |
6 |
private static var _allowInstantiation:Boolean; |
7 |
|
8 |
public static function get instance():ObjectPool |
9 |
{
|
10 |
if (!_instance) |
11 |
{
|
12 |
_allowInstantiation = true; |
13 |
_instance = new ObjectPool(); |
14 |
_allowInstantiation = false; |
15 |
}
|
16 |
|
17 |
return _instance; |
18 |
}
|
19 |
|
20 |
public function ObjectPool() |
21 |
{
|
22 |
if (!_allowInstantiation) |
23 |
{
|
24 |
throw new Error("Trying to instantiate a Singleton!"); |
25 |
}
|
26 |
}
|
27 |
|
28 |
}
|
29 |
|
30 |
}
|
Variabel _allowInstantiation
adalah inti dari implementasi Singleton ini: privat, jadi hanya kelas sendiri yang dapat dimodifikasi, dan satu-satunya tempat di mana ia harus dimodifikasi adalah sebelum membuat instance pertama dari itu.
Kita sekarang harus memutuskan bagaimana menahan kolam di dalam kelas ini. Karena ini bersifat global (mis. Dapat menyatukan objek apa pun dalam aplikasi Anda), kita harus terlebih dahulu menemukan cara untuk selalu memiliki nama unik untuk setiap kumpulan. Bagaimana cara melakukannya? Ada banyak cara, tetapi yang terbaik yang saya temukan sejauh ini adalah menggunakan nama kelas objek sendiri sebagai nama kumpulan. Dengan begitu, kita bisa memiliki kolam "Partikel", kolam "Musuh" dan seterusnya... tapi ada masalah lain. Nama kelas hanya harus unik di dalam paket mereka, jadi misalnya kelas "BaseObject" dalam paket "musuh" dan kelas "BaseObject" dalam paket "structure" akan diizinkan. Itu akan menyebabkan masalah di kolam renang.
Gagasan menggunakan nama kelas sebagai pengidentifikasi untuk kumpulan masih bagus, dan ini adalah di mana flash.utils.getQualifiedClassName()
datang untuk membantu kita. Pada dasarnya fungsi ini menghasilkan string dengan nama kelas penuh, termasuk paket apa pun. Jadi sekarang, kita dapat menggunakan nama kelas yang memenuhi syarat setiap objek sebagai pengidentifikasi untuk kumpulan masing-masing! Inilah yang akan kita tambahkan di langkah selanjutnya.
Langkah 7: Membuat Pools
Sekarang kita memiliki cara untuk mengidentifikasi kumpulan, saatnya untuk menambahkan kode yang membuatnya. Kumpulan objek kita harus cukup fleksibel untuk mendukung kumpulan statis dan dinamis (kita membicarakannya pada Langkah 3, ingat?). Kita juga harus dapat menyimpan ukuran masing-masing kelompok dan berapa banyak objek aktif yang ada di masing-masing kelompok. Solusi yang bagus untuk itu adalah membuat kelas privat dengan semua informasi ini dan menyimpan semua kumpulan dalam sebuah Objek
:
1 |
package
|
2 |
{
|
3 |
public class ObjectPool |
4 |
{
|
5 |
private static var _instance:ObjectPool; |
6 |
private static var _allowInstantiation:Boolean; |
7 |
|
8 |
private var _pools:Object; |
9 |
|
10 |
public static function get instance():ObjectPool |
11 |
{
|
12 |
if (!_instance) |
13 |
{
|
14 |
_allowInstantiation = true; |
15 |
_instance = new ObjectPool(); |
16 |
_allowInstantiation = false; |
17 |
}
|
18 |
|
19 |
return _instance; |
20 |
}
|
21 |
|
22 |
public function ObjectPool() |
23 |
{
|
24 |
if (!_allowInstantiation) |
25 |
{
|
26 |
throw new Error("Trying to instantiate a Singleton!"); |
27 |
}
|
28 |
|
29 |
_pools = {}; |
30 |
}
|
31 |
|
32 |
}
|
33 |
|
34 |
}
|
35 |
|
36 |
class PoolInfo |
37 |
{
|
38 |
public var items:Vector.<IPoolable>; |
39 |
public var itemClass:Class; |
40 |
public var size:uint; |
41 |
public var active:uint; |
42 |
public var isDynamic:Boolean; |
43 |
|
44 |
public function PoolInfo(itemClass:Class, size:uint, isDynamic:Boolean = true) |
45 |
{
|
46 |
this.itemClass = itemClass; |
47 |
items = new Vector.<IPoolable>(size, !isDynamic); |
48 |
this.size = size; |
49 |
this.isDynamic = isDynamic; |
50 |
active = 0; |
51 |
|
52 |
initialize(); |
53 |
}
|
54 |
|
55 |
private function initialize():void |
56 |
{
|
57 |
for (var i:int = 0; i < size; i++) |
58 |
{
|
59 |
items[i] = new itemClass(); |
60 |
}
|
61 |
}
|
62 |
}
|
Kode di atas membuat kelas privat yang akan berisi semua informasi tentang kumpulan. Kita juga membuat objek _pools
untuk menampung semua kumpulan objek. Di bawah ini kita akan membuat fungsi yang mendaftarkan kumpulan di kelas:
1 |
public function registerPool(objectClass:Class, size:uint = 1, isDynamic:Boolean = true):void |
2 |
{
|
3 |
if (!(describeType(objectClass).factory.implementsInterface.(@type == "IPoolable").length() > 0)) |
4 |
{
|
5 |
throw new Error("Can't pool something that doesn't implement IPoolable!"); |
6 |
return; |
7 |
}
|
8 |
|
9 |
var qualifiedName:String = getQualifiedClassName(objectClass); |
10 |
|
11 |
if (!_pools[qualifiedName]) |
12 |
{
|
13 |
_pools[qualifiedName] = new PoolInfo(objectClass, size, isDynamic); |
14 |
}
|
15 |
}
|
Kode ini terlihat sedikit rumit, tetapi jangan panik. Semuanya dijelaskan di sini. Pernyataan if
pertama terlihat sangat aneh. Anda mungkin belum pernah melihat fungsi-fungsi itu sebelumnya, jadi inilah fungsinya:
- Fungsi descriptionType() membuat XML yang berisi semua informasi tentang objek yang dilewati.
- Dalam kasus kelas, semua yang ada di dalamnya terkandung dalam tag
factory
. - Di dalamnya, XML menjelaskan semua interface yang mengimplementasikan kelas dengan tag
implementsInterface
. - Kita melakukan pencarian cepat untuk melihat apakah interface
IPoolable
ada di antara mereka. Jika demikian, maka kita tahu bahwa kita dapat menambahkan kelas itu ke kumpulan, karena kita akan dapat berhasil melemparkannya sebagaiIObject
.
Kode setelah pemeriksaan ini hanya membuat entri di dalam _pools
jika belum ada. Setelah itu, konstruktor PoolInfo
memanggil fungsi initialize()
di dalam kelas itu, secara efektif membuat kumpulan dengan ukuran yang diinginkan. Sekarang siap digunakan!
Langkah 8: Mendapatkan Objek
Pada langkah terakhir kita dapat membuat fungsi yang mendaftarkan kumpulan objek, tapi sekarang kita perlu mendapatkan objek untuk menggunakannya. Ini sangat mudah: kita mendapatkan objek jika kolam tidak kosong dan mengembalikannya. Jika pool kosong, periksa apakah itu dinamis; jika demikian, tambah ukurannya, dan kemudian buat objek baru dan mengembalikannya. Jika tidak, kembalikan nol. (Anda juga dapat memilih untuk melempar kesalahan, tetapi lebih baik mengembalikan null dan membuat kode Anda mengatasi situasi ini saat itu terjadi.)
Inilah fungsi getObj()
:
1 |
public function getObj(objectClass:Class):IPoolable |
2 |
{
|
3 |
var qualifiedName:String = getQualifiedClassName(objectClass); |
4 |
|
5 |
if (!_pools[qualifiedName]) |
6 |
{
|
7 |
throw new Error("Can't get an object from a pool that hasn't been registered!"); |
8 |
return; |
9 |
}
|
10 |
|
11 |
var returnObj:IPoolable; |
12 |
|
13 |
if (PoolInfo(_pools[qualifiedName]).active == PoolInfo(_pools[qualifiedName]).size) |
14 |
{
|
15 |
if (PoolInfo(_pools[qualifiedName]).isDynamic) |
16 |
{
|
17 |
returnObj = new objectClass(); |
18 |
|
19 |
PoolInfo(_pools[qualifiedName]).size++; |
20 |
PoolInfo(_pools[qualifiedName]).items.push(returnObj); |
21 |
}
|
22 |
else
|
23 |
{
|
24 |
return null; |
25 |
}
|
26 |
}
|
27 |
else
|
28 |
{
|
29 |
returnObj = PoolInfo(_pools[qualifiedName]).items[PoolInfo(_pools[qualifiedName]).active]; |
30 |
|
31 |
returnObj.renew(); |
32 |
}
|
33 |
|
34 |
PoolInfo(_pools[qualifiedName]).active++; |
35 |
|
36 |
return returnObj; |
37 |
}
|
Dalam fungsinya, pertama-tama kita periksa apakah pool benar-benar ada. Dengan asumsi kondisi terpenuhi, periksa apakah pool kosong: jika itu dinamis, buat objek baru dan tambahkan ke dalam pool. Jika kumpulan tidak dinamis, hentikan kode di sana dan hanya mengembalikan nol. Jika pool masih memiliki objek, kita mendapatkan objek yang paling dekat dengan awal pool dan memanggil renew()
di atasnya. Ini penting: alasan kita memanggil renew()
pada objek yang sudah ada di pool adalah untuk menjamin bahwa objek ini akan diberikan pada keadaan "dapat digunakan".
Anda mungkin bertanya-tanya: mengapa Anda tidak menggunakan cek keren itu dengan descriptionType()
di fungsi ini? Yah, jawabannya sederhana: descriptionType()
membuat XML setiap kali kita menyebutnya, jadi sangat penting untuk menghindari penciptaan objek yang menggunakan banyak memori dan yang tidak bisa kita kontrol. Selain itu, hanya memeriksa untuk melihat apakah kumpulan benar-benar ada sudah cukup: jika kelas yang lulus tidak menerapkan IPoolable
, itu berarti bahkan kita tidak akan dapat membuat pooluntuk itu. Jika tidak ada kumpulan untuk itu, maka kita pasti menangkap kasus ini dalam pernyataan if
di awal fungsi.
Kita sekarang dapat memodifikasi kelas Main
dan menggunakan kumpulan objek! Coba lihat:
1 |
private function init(e:Event = null):void |
2 |
{
|
3 |
removeEventListener(Event.ADDED_TO_STAGE, init); |
4 |
// entry point
|
5 |
stage.addEventListener(MouseEvent.CLICK, createParticles); |
6 |
addEventListener(Event.ENTER_FRAME, updateParticles); |
7 |
|
8 |
_oldTime = getTimer(); |
9 |
|
10 |
ObjectPool.instance.registerPool(Particle, 200, true); |
11 |
}
|
12 |
|
13 |
private function createParticles(e:MouseEvent):void |
14 |
{
|
15 |
var tempParticle:Particle; |
16 |
|
17 |
for (var i:int = 0; i < 10; i++) |
18 |
{
|
19 |
tempParticle = ObjectPool.instance.getObj(Particle) as Particle; |
20 |
tempParticle.x = e.stageX; |
21 |
tempParticle.y = e.stageY; |
22 |
|
23 |
addChild(tempParticle); |
24 |
}
|
25 |
}
|
Tekan kompilasi dan buat profil penggunaan memori! Inilah yang saya dapatkan:



Cukup keren, bukan?
Langkah 9: Mengembalikan Objek ke Pool
Kita telah berhasil mengimplementasikan kumpulan objek yang memberikan objek. Itu luar biasa! Tapi itu belum berakhir. Kita masih hanya mendapatkan objek, tetapi tidak pernah mengembalikannya saat kita tidak membutuhkannya lagi. Saatnya menambahkan fungsi untuk mengembalikan objek di dalam ObjectPool.as
:
1 |
public function returnObj(obj:IPoolable):void |
2 |
{
|
3 |
var qualifiedName:String = getQualifiedClassName(obj); |
4 |
|
5 |
if (!_pools[qualifiedName]) |
6 |
{
|
7 |
throw new Error("Can't return an object from a pool that hasn't been registered!"); |
8 |
return; |
9 |
}
|
10 |
|
11 |
var objIndex:int = PoolInfo(_pools[qualifiedName]).items.indexOf(obj); |
12 |
|
13 |
if (objIndex >= 0) |
14 |
{
|
15 |
if (!PoolInfo(_pools[qualifiedName]).isDynamic) |
16 |
{
|
17 |
PoolInfo(_pools[qualifiedName]).items.fixed = false; |
18 |
}
|
19 |
|
20 |
PoolInfo(_pools[qualifiedName]).items.splice(objIndex, 1); |
21 |
|
22 |
obj.destroy(); |
23 |
|
24 |
PoolInfo(_pools[qualifiedName]).items.push(obj); |
25 |
|
26 |
if (!PoolInfo(_pools[qualifiedName]).isDynamic) |
27 |
{
|
28 |
PoolInfo(_pools[qualifiedName]).items.fixed = true; |
29 |
}
|
30 |
|
31 |
PoolInfo(_pools[qualifiedName]).active--; |
32 |
}
|
33 |
}
|
Mari kita pergi melalui fungsi: hal pertama adalah untuk memeriksa apakah ada kumpulan objek yang dilewati. Anda terbiasa dengan kode itu - satu-satunya perbedaan adalah sekarang kita menggunakan objek alih-alih kelas untuk mendapatkan nama yang memenuhi syarat, tetapi itu tidak mengubah output).
Selanjutnya, kita mendapatkan indeks item di pool. Jika tidak ada di kolam, kita abaikan saja. Setelah kita memverifikasi bahwa objek ada di kolam, kita harus memecah kolam di tempat objek saat ini berada dan memasukkan kembali objek di akhir itu. Dan mengapa? Karena kita menghitung objek yang digunakan dari awal kolam, kita perlu mengatur ulang kolam untuk membuat semua objek yang dikembalikan dan tidak terpakai berada di ujungnya. Dan itulah yang dilakukan dalam fungsi ini.
Untuk kumpulan objek statis, kita membuat objek Vector
yang memiliki panjang tetap. Karena itu, kita tidak dapat splice()
dan push()
objek kembali. Solusi untuk ini adalah fixed
properti tetap Vector
tersebut menjadi false
, menghapus objek dan menambahkannya kembali di akhir, dan kemudian mengubah properti kembali ke true
. Kita juga perlu mengurangi jumlah objek aktif. Setelah itu, kita selesai mengembalikan objek.
Sekarang kita telah membuat kode untuk mengembalikan objek, kita dapat membuat partikel kita kembali ke kolam setelah mencapai akhir masa hidupnya. Di dalam Particle.as
:
1 |
public function update(timePassed:uint):void |
2 |
{
|
3 |
// Making the particle move
|
4 |
x += Math.cos(_angle) * _speed * timePassed / 1000; |
5 |
y += Math.sin(_angle) * _speed * timePassed / 1000; |
6 |
|
7 |
// Small easing to make movement look pretty
|
8 |
_speed -= 120 * timePassed / 1000; |
9 |
|
10 |
// Taking care of lifetime and removal
|
11 |
_lifeTime -= timePassed; |
12 |
|
13 |
if (_lifeTime <= 0) |
14 |
{
|
15 |
parent.removeChild(this); |
16 |
|
17 |
ObjectPool.instance.returnObj(this); |
18 |
}
|
19 |
}
|
Perhatikan bahwa kita menambahkan panggilan ke ObjectPool.instance.returnObj()
di sana. Itulah yang membuat objek kembali dengan sendirinya ke kolam. Kita sekarang dapat menguji dan membuat profil aplikasi:



Dan kita mulai! Memori stabil bahkan ketika ratusan klik dilakukan!
Kesimpulan
Anda sekarang tahu cara membuat dan menggunakan kumpulan objek untuk menjaga agar penggunaan memori aplikasi Anda stabil. Kelas yang dibangun dapat digunakan di mana saja dan sangat mudah untuk menyesuaikan kode Anda dengan itu: di awal aplikasi Anda, buat kumpulan objek untuk setiap jenis objek yang ingin Anda kumpulan, dan setiap kali ada kata kunci baru
(yang berarti pembuatan instance), ganti dengan panggilan ke fungsi yang mendapatkan objek untuk Anda. Jangan lupa untuk mengimplementasikan metode yang diperlukan oleh interface IPoolable
!
Menjaga stabilitas penggunaan memori Anda sangat penting. Ini menghemat banyak masalah nanti dalam proyek Anda ketika semuanya mulai berantakan dengan contoh-contoh yang tidak didaur ulang masih menanggapi pendengar acara, objek mengisi memori yang Anda miliki untuk digunakan dan dengan pengumpul sampah berjalan dan memperlambat semuanya. Rekomendasi yang baik adalah untuk selalu menggunakan pengumpulan objek dari sekarang, dan Anda akan melihat hidup Anda akan jauh lebih mudah.
Perhatikan juga bahwa meskipun tutorial ini ditujukan untuk Flash, konsep yang dikembangkan di dalamnya bersifat global: Anda dapat menggunakannya pada aplikasi AIR, aplikasi seluler, dan di mana saja yang sesuai. Terima kasih sudah membaca!