Reference Counting: Cara Cocoa, WinRT, dan COM Mengelola Memori Objek

Reference Counting: Cara Cocoa, WinRT, dan COM Mengelola Memori Objek

Artikel ini membahas tentang teknik reference counting yang digunakan dalam tiga framework berbeda: Cocoa untuk pengembangan aplikasi di platform Apple, WinRT untuk pengembangan aplikasi di platform Windows, dan Component Object Model (COM) yang digunakan dalam sistem operasi Windows. Kami akan membahas bagaimana ketiga framework ini menggunakan reference counting untuk mengelola memori objek dan membantu menghindari kebocoran memori.

John Doe
John Doe

Ada beberapa cara sebuah program mengelola memori dan siklus hidup sebuh objek dalam suatu program. Java dan Go memilih menggunakan Garbage Collector yang merupakan mekanisme otomatis untuk mengelola memori dan menghapus objek yang tidak lagi digunakan secara otomatis. Sementara itu, bahasa pemrograman seperti C dan C++ menggunakan teknik manual memory management dengan penggunaan fungsi alokasi seperti malloc dan free atau kata kunci seperti new dan delete untuk mengelola alokasi dan dealokasi memori.Bahasa seperti Rust bahkan memilih menggunakan compiler untuk melacak lifetime dari suatu variabel.

Selain cara-cara di atas ada satu cara lain yang memilih mengelola memori objek dengan reference counting. Reference Counting adalah suatu cara untuk mengelola siklus hidup objek dengan cara menghitung berapa kali sebuah objek direferensikan oleh objek lain. Ketika sebuah objek dibuat, referensi countnya bernilai satu. Setiap kali sebuah objek lain mereferensikan objek tersebut, reference count akan bertambah satu. Sebaliknya, ketika sebuah objek tidak lagi mereferensikan objek tersebut, reference count akan berkurang satu. Ketika reference count bernilai nol, objek akan dihapus dari memori.

Contoh Implementasi Reference Counting dalam C

Reference counting bisa diekspresikan dengan pseudo-code seperti di bawah ini.


struct person_rc_t {
  int age;
  int id;
  char name[256];

  // hitungan referensi
  int ref;
};

// konstruksi objek
person_rc_t *create_new_person(int age, int id, char *name, size_t charlen) {
  size_t sz = charnel > 255 ? 255 : charlen;
  person_rc_t *p = malloc(sizeof(struct person_rc_t));
  p->age = age; 
  p->id = id; 
  memcpy(p->name, name, sz);
  p->name[sz] = '\0';

  p->ref = 1;

  return p;
}

// mengakuisisi objek
person_rc_t *acquire_person(struct person_rc_t *p) {
  ++ p->ref; 

  return p;
}

// melepas objek
void release_person(struct person_rc_t *p) {
  -- p->ref;

  if (p == 0) {
    free(p);
  }
}

void use_person(void) {
  person_rc_t *person = create_new_person(34, 4, "Eko", 3);

  person_rc_t *another = acquire_person(person);

  /* lakukan sesuatu dengan another dan person */

  release_person(another); // ref == 1 
  release_person(person);  // ref == 0
}

Di dalam tipe person_rc_t tersebut terdapat variabel untuk menghitung referensi, di dalam kasus kita di atas, referensi disimpan dalam variabel ref di dalam tipe person_rc_t. Untuk 'mendapatkan' referensi ke pointer person pemanggil harus menggunakan fungsi acquire_person yang akan menambahkan satu referensi. Dan jika sudah selesai memakai, maka kita memakai release_person yang akan mengurangi referensi. Ketika referensi 0, fungsi free akan dipanggil.

Sebenarnya teknik reference counting ini juga dipakai di Garbage Collector1. Perbedaannya, ada di algoritmanya. Di sini kita merilis manual, sementara di GC, algoritma yang dipakai adalah algoritma yang otomatis akan membersihkan objek seperti Concurrent Mark and Sweep2.

Penggunaan Reference Counting dalam COM

COM (Component Object Model) adalah teknologi yang dibuat oleh Microsoft untuk membuat aplikasi bisa berkomunikasi satu sama lain. Nama-nama lain yang sering dipakai oleh Microsoft adalah OLE, OLE2, OLE Automation. COM dibuat dengan ABI yang kompatibel dengan ABI dari Microsoft C++ dan ditujukan supaya bisa dikonsumsi oleh bahasa apapun yang mendukung COM seperti Visual Basic dan C#.

Komponen yang dibuat dalam COM berorientasi objek dan dikelola dengan menggunakan reference counting. Superclass dari objek COM adalah interface yang didefinisikan kurang lebih seperti ini.

#include <unknwn.h>

class IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(
        /* [in] */ REFIID riid,
        /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) = 0;

    virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;

    virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
};

Abaikan anotasi-anotasi seperti _COM_Outptr_ dan RPC_FAR. Saya akan menyoroti dua fungsi paling bawah yaitu AddRef dan Release. Kedua fungsi tersebut adalah fungsi untuk melakukan reference counting. Sementara QueryInterface dipergunakan untuk mengkonversi dari satu antarmuka ke antarmuka yang lain.

Contoh penggunaan COM bisa dilihat di kode di bawah ini yang membuat pintasan(shortcut) di destop.



#include <Windows.h>
#include <ShlObj.h>
#include <string>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    // Inisialisasi COM
    CoInitialize(NULL);

    // Buat instance dari objek IShellLink
    IShellLink *pShellLink = nullptr;
    HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&pShellLink);
    if (SUCCEEDED(hr))
    {
        // Atur properti pintasan
        pShellLink->SetPath("C:\\Windows\\System32\\notepad.exe");
        pShellLink->SetDescription("Pintasan ke Notepad");

        // Ambil antarmuka IPersistFile
        IPersistFile *pPersistFile = nullptr;
        hr = pShellLink->QueryInterface(IID_IPersistFile, (void **)&pPersistFile);
        if (SUCCEEDED(hr))
        {
            // Simpan pintasan di desktop
            WCHAR desktopPath[MAX_PATH];
            SHGetSpecialFolderPath(NULL, desktopPath, CSIDL_DESKTOP, FALSE);
            std::wstring shortcutPath = std::wstring(desktopPath) + L"\\Pintasan Notepad.lnk";
            hr = pPersistFile->Save(shortcutPath.c_str(), TRUE);

            // Lepaskan antarmuka IPersistFile
            pPersistFile->Release();

            if (SUCCEEDED(hr))
            {
                MessageBox(NULL, L"Pintasan berhasil dibuat", L"Informasi", MB_OK | MB_ICONINFORMATION);
            }
            else
            {
                MessageBox(NULL, L"Gagal menyimpan pintasan", L"Kesalahan", MB_OK | MB_ICONERROR);
            }
        }
        else
        {
            MessageBox(NULL, L"Gagal mengambil antarmuka IPersistFile", L"Kesalahan", MB_OK | MB_ICONERROR);
        }

        // Lepaskan antarmuka IShellLink
        pShellLink->Release();
    }
    else
    {
        MessageBox(NULL, L"Gagal membuat objek IShellLink", L"Kesalahan", MB_OK | MB_ICONERROR);
    }

    // Hentikan inisialisasi COM
    CoUninitialize();

    return 0;
}

Ketika antarmuka IShellLink dikonversi menjadi IPersistFile dengan QueryInterface, referensi dari objek yang dibuat dengan CoCreateInstance bertambah satu. Dan ketika kita selesai memakai objek IShellLink, kita lepas dengan Release. Di baris Release terakhir, objek akan dibebaskan dari memori.

API di Windows modern yang dibuat oleh Microsoft sebagian besar adalah COM API. Bahkan platform baru, yaitu UWP (Universal Windows Platform) meninggalkan paradigma Win32 API yang berbasis C dan antarmuka pemrogramannya adalah iterasi dari COM.

Komponen-komponen COM Moderen biasanya dibuat menggunakan C++. Dan Microsoft berinvestasi cukup besar dalam bahasa C++. Untuk WinRT, Microsoft bahkan membuat projection dari C++20 yang dinamai C++/WinRT.

Video di bawah ini menerangkan lebih lanjut soal keputusan Microsoft untuk memakai C++20 dan C++/WinRT. Video dalam Bahasa Inggris.

Penggunaan Reference Counting di platform Apple

Apple, menggunakan paradigma ini dari platform yang dibuat oleh NextSTEP. Di iklannya, NextSTEP dipromosikan sebagai "platform berorientasi objek". Maksud dari slogan kurang lebih adalah semua hal adalah objek dan dikelola dengan reference counting. Mari kita lihat program sederhana di zaman NextStep.


#include <appkit/Application.h>
#include <appkit/Window.h>

@interface AppDelegate : Object
{
    Window *window; // deklarasi instance variable untuk window
}

- (void)applicationDidFinishLaunching: (NSNotification *)notification; // method untuk mengatur window ketika aplikasi selesai diluncurkan
- (void)applicationWillTerminate: (NSNotification *)notification; // method untuk membersihkan objek window ketika aplikasi akan ditutup

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    window = [[Window alloc] initWithContentRect:MakeRect(0, 0, 300, 200)
                                         style: (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask)
                                      backing: NXBuffered
                                        defer: NO];
    [window setTitle: @"NextSTEP Window"]; // atur judul window
    [window center]; // letakkan window di tengah layar
    [window makeKeyAndOrderFront: nil]; // tampilkan window di depan

}

- (void)applicationWillTerminate:(NSNotification *)notification {
    [window release]; // bebaskan objek window sebelum aplikasi ditutup
}

@end

int main(int argc, const char *argv[]) {
    id application = [Application sharedApplication]; // buat objek aplikasi
    id appDelegate = [[AppDelegate alloc] init]; // buat objek delegate
    [application setDelegate: appDelegate]; // atur delegate aplikasi
    [application run]; // jalankan aplikasi
    [appDelegate release]; // bebaskan objek delegate
    return 0;
}

Di kode objective-C di atas, kita membuat objek delegate untuk kemudian di set ke objek application. Ketika apilkasi mau diterminasi, objek window dilepaskan dengan memanggil release. Tidak ada metode semacam QueryInterface seperti di Windows, karena objective-c adalah bahasa dengan tipe lemah.

Automatic Reference Counting

Di macOS moderen, keperluan reference counting ditangani oleh fitur compiler yang dinamai Automatic Reference Counting (ARC). ARC ini akan mendeteksi kapan harus di-retain dan release. Ini mengatasi permasalahan yang dinamakan dengan retain cycle yaitu keadaan dimana dua objek saling me-retain satu sama lain sehingga tidak bisa dilepaskan.

Di macOS moderen, kode NextSTEP di atas menjadi seperti di bawah ini:

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (nonatomic, strong) NSWindow *window; // deklarasi instance variable window dengan property

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    self.window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 300, 200)
                                               styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable // atur gaya window dengan NSWindowStyleMask
                                                 backing:NSBackingStoreBuffered
                                                   defer:NO];
    [self.window setTitle:@"NextSTEP Window"]; // atur judul window
    [self.window center]; // letakkan window di tengah layar
    [self.window makeKeyAndOrderFront:nil]; // tampilkan window di depan

}

- (void)applicationWillTerminate:(NSNotification *)notification {
    self.window = nil; // bebaskan objek window ketika aplikasi akan ditutup
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSApplication *application = [NSApplication sharedApplication]; // buat objek aplikasi
        AppDelegate *appDelegate = [[AppDelegate alloc] init]; // buat objek delegate
        [application setDelegate:appDelegate]; // atur delegate aplikasi
        [application run]; // jalankan aplikasi
    }
    return 0;
}

Ada anotasi baru seperti (nonatomic, strong) yang menandakan bahwa objek window dipunyai (retained) oleh AppDelegate dan siklus hidupnya selaras dengan siklus AppDelegate. Di kode di atas, walaupun tetap ada reference counting, penulisan retain dan release akan dikerjakan secara implisit oleh compiler. Kode menjadi lebih ringkas dan lebih mudah dibaca. Bahkan, di bahasa terbaru yang dibuat oleh Apple, Swift. Kode di atas jadi lebih singkat lagi.


import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!

    func applicationDidFinishLaunching(_ notification: Notification) {
        window = NSWindow(contentRect: NSMakeRect(0, 0, 300, 200),
                          styleMask: [.titled, .closable, .resizable],
                          backing: .buffered,
                          defer: false)
        window.title = "NextSTEP Window"
        window.center()
        window.makeKeyAndOrderFront(nil)
    }

    func applicationWillTerminate(_ notification: Notification) {
        window = nil
    }
}

Mungkin karena inilah, Apple pernah bikin promosi di mana, walaupun pakai prinsip yang sama, yaitu reference counting. Memrogram di Apple terasa lebih mudah karena sintaks Objective-C dan Swift lebih mudah dipahami daripada C++.

Kesimpulan

Dalam artikel ini, kita telah membahas tentang teknik reference counting dan bagaimana teknik ini digunakan untuk mengelola memori objek dalam beberapa framework seperti Cocoa, WinRT, dan COM. Dalam reference counting, objek akan dihapus dari memori ketika referensi objek tersebut bernilai nol.

Kita juga melihat contoh implementasi reference counting pada bahasa pemrograman C dan bagaimana teknik ini digunakan pada teknologi COM dan platform Apple. Pada macOS moderen, keperluan reference counting ditangani oleh fitur compiler yang dinamai Automatic Reference Counting (ARC).

Meskipun teknik reference counting memiliki keuntungan dalam mengelola memori objek, terdapat juga beberapa kelemahan seperti retain cycle yang dapat terjadi pada beberapa kasus. Dalam kasus seperti itu, pengguna harus memastikan untuk membebaskan referensi objek dengan benar untuk mencegah kebocoran memori.


Footnotes

  1. Salgado, Pablo Galindo. Garbage Collection Design. Retrieved April 4, 2023.

  2. Jones, Richard. 2001. Antony Hosking, and Eliott Moss. The Garbage Collection Handbook. Amazon

programming
cocoa
winrt
com

Previous Article

C Kuno? Ini 5 Alasan Bahasa C Masih Relevan di Masa Sekarang
C Kuno? Ini 5 Alasan Bahasa C Masih Relevan di Masa Sekarang

Bahasa pemrograman C kerap dianggap usang, tetapi sebenarnya masih relevan dan digunakan luas dalam teknologi. Berikut lima alasan mengapa C penting untuk developer.

Next Article

Pembuatan Aplikasi Cross Platform Dengan Menggunakan C dan CMake
Pembuatan Aplikasi Cross Platform Dengan Menggunakan C dan CMake

Dalam artikel ini, kita akan membahas cara membuat aplikasi lintas platform dengan menggunakan C dan CMake.

Subscribe Kanal Koding Aja Dulu!

Berlangganan ke channel YouTube kami dan dapatkan akses ke tutorial terbaru, tips, dan trik pemrograman sistem, microservices, dan low level programming.

Subscribe Sekarang