NSObject + memuat dan + menginisialisasi - Apa yang mereka lakukan?

115

Saya tertarik untuk memahami keadaan yang membuat pengembang mengganti + menginisialisasi atau + memuat. Dokumentasi memperjelas metode ini dipanggil untuk Anda oleh runtime Objective-C, tetapi hanya itu yang jelas dari dokumentasi metode tersebut. :-)

Rasa ingin tahu saya datang dari melihat kode contoh Apple - MVCNetworking. Kelas model mereka memiliki +(void) applicationStartupmetode. Ia melakukan beberapa housekeeping pada filesystem, membaca NSDefaults, dll dll ... dan, setelah mencoba menggunakan metode kelas NSObject, sepertinya pekerjaan kebersihan ini mungkin baik-baik saja untuk dimasukkan ke dalam + load.

Saya memang memodifikasi proyek MVCNetworking, menghapus panggilan di App Delegate ke + applicationStartup, dan memasukkan bit housekeeping ke + memuat ... komputer saya tidak terbakar, tetapi itu tidak berarti itu benar! Saya berharap untuk mendapatkan pemahaman tentang seluk-beluk, gotcha, dan apa pun di sekitar metode penyiapan khusus yang harus Anda panggil versus + memuat atau + menginisialisasi.


Untuk + memuat dokumentasi mengatakan:

Pesan muat dikirim ke kelas dan kategori yang dimuat secara dinamis dan ditautkan secara statis, tetapi hanya jika kelas atau kategori yang baru dimuat mengimplementasikan metode yang dapat merespons.

Kalimat ini kludgey dan sulit diurai jika Anda tidak tahu arti sebenarnya dari semua kata. Tolong!

  • Apa yang dimaksud dengan "yang dimuat secara dinamis dan terhubung secara statis?" Dapatkah sesuatu dimuat secara dinamis DAN ditautkan secara statis, atau apakah keduanya saling eksklusif?

  • "... kelas atau kategori yang baru dimuat mengimplementasikan metode yang dapat merespons" Metode apa? Menanggapi bagaimana?


Adapun + inisialisasi, dokumentasi mengatakan:

menginisialisasi itu dipanggil hanya sekali per kelas. Jika Anda ingin melakukan inisialisasi independen untuk kelas dan untuk kategori kelas, Anda harus mengimplementasikan metode beban.

Saya mengartikan ini sebagai, "jika Anda mencoba untuk mengatur kelas ... jangan gunakan inisialisasi." Oke, baiklah. Kapan atau mengapa saya mengganti inisialisasi?

edelaney05
sumber

Jawaban:

184

The loadpesan

Runtime mengirimkan loadpesan ke setiap objek kelas, segera setelah objek kelas dimuat di ruang alamat proses. Untuk kelas yang merupakan bagian dari file program yang dapat dieksekusi, runtime mengirimkan loadpesan di awal masa proses. Untuk kelas yang berada di pustaka bersama (dimuat secara dinamis), runtime mengirim pesan pemuatan tepat setelah pustaka bersama dimuat ke ruang alamat proses.

Selain itu, runtime hanya mengirim loadke objek kelas jika objek kelas itu sendiri mengimplementasikan loadmetode tersebut. Contoh:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)load {
    NSLog(@"in Superclass load");
}

@end

@implementation Subclass

// ... load not implemented in this class

@end

Runtime mengirim loadpesan ke Superclassobjek kelas. Itu tidak mengirim loadpesan ke Subclassobjek kelas, meskipun Subclassmewarisi metode dari Superclass.

Runtime mengirim loadpesan ke objek kelas setelah itu mengirim loadpesan ke semua objek superclass kelas (jika objek superclass itu diterapkan load) dan semua objek kelas di pustaka bersama yang Anda tautkan. Tetapi Anda belum tahu kelas lain mana di executable Anda sendiri yang telah menerima load.

Setiap kelas yang proses Anda muat ke dalam ruang alamatnya akan menerima loadpesan, jika mengimplementasikan loadmetode, terlepas dari apakah proses Anda membuat penggunaan lain dari kelas tersebut.

Anda dapat melihat bagaimana runtime mencari loadmetode sebagai kasus khusus di _class_getLoadMethoddari objc-runtime-new.mm, dan memanggilnya langsung dari call_class_loadsdalam objc-loadmethod.mm.

Runtime juga menjalankan loadmetode dari setiap kategori yang dimuatnya, meskipun beberapa kategori pada implementasi kelas yang sama load. Ini tidak biasa. Biasanya, jika dua kategori mendefinisikan metode yang sama pada kelas yang sama, salah satu metode akan “menang” dan digunakan, dan metode lainnya tidak akan pernah dipanggil.

The initializeMetode

Runtime memanggil initializemetode pada objek kelas tepat sebelum mengirim pesan pertama (selain loadatau initialize) ke objek kelas atau instance kelas apa pun. Pesan ini dikirim menggunakan mekanisme normal, jadi jika kelas Anda tidak mengimplementasikan initialize, tetapi mewarisi dari kelas yang melakukannya, maka kelas Anda akan menggunakan superkelasnya initialize. Runtime akan mengirim initializeke semua kelas super terlebih dahulu (jika superkelas belum dikirim initialize).

Contoh:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)initialize {
    NSLog(@"in Superclass initialize; self = %@", self);
}

@end

@implementation Subclass

// ... initialize not implemented in this class

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Subclass *object = [[Subclass alloc] init];
    }
    return 0;
}

Program ini mencetak dua baris keluaran:

2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass

Karena sistem mengirim initializemetode dengan malas, kelas tidak akan menerima pesan kecuali jika program Anda benar-benar mengirim pesan ke kelas (atau subkelas, atau instance kelas atau subkelas). Dan pada saat Anda menerima initialize, setiap kelas dalam proses Anda seharusnya sudah menerima load(jika sesuai).

Cara kanonik untuk mengimplementasikannya initializeadalah ini:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

Inti dari pola ini adalah untuk menghindari Someclassinisialisasi ulang itu sendiri jika memiliki subclass yang tidak diimplementasikan initialize.

Runtime mengirim initializepesan dalam _class_initializefungsi di objc-initialize.mm. Anda dapat melihat bahwa itu digunakan objc_msgSenduntuk mengirimnya, yang merupakan fungsi pengiriman pesan normal.

Bacaan lebih lanjut

Lihat Tanya Jawab Jumat Mike Ash tentang topik ini.

merampok mayoff
sumber
25
Anda harus mencatat bahwa +loaddikirim secara terpisah untuk kategori; artinya, setiap kategori di kelas mungkin berisi +loadmetodenya sendiri .
Jonathan Grynspan
1
Perhatikan juga bahwa initializeakan dipanggil dengan benar oleh sebuah loadmetode, jika perlu, karena loadreferensi pembuatan ke entitas yang tidak diinisialisasi. Ini bisa (anehnya, tapi masuk akal) mengarah pada initializelari sebelumnya load! Itulah yang saya amati. Ini sepertinya bertentangan dengan "Dan pada saat Anda menerima initialize, setiap kelas dalam proses Anda seharusnya sudah menerima load(jika sesuai)."
Benjohn
5
Anda menerima loadlebih dulu. Anda kemudian dapat menerima initializesaat loadmasih berjalan.
merampok mayoff
1
@robmayoff Bukankah kita perlu menambahkan baris [super inisialisasi] dan [super load], di dalam metode masing-masing?
sialan
1
Itu biasanya ide yang buruk, karena runtime telah mengirimkan kedua pesan tersebut ke semua superclass Anda sebelum mengirimkannya kepada Anda.
merampok mayoff
17

Artinya adalah jangan menimpa +initializedalam kategori, Anda mungkin akan merusak sesuatu.

+loaddipanggil sekali per kelas atau kategori yang diimplementasikan +load, segera setelah kelas atau kategori tersebut dimuat. Jika tertulis "ditautkan secara statis", artinya dikompilasi ke dalam biner aplikasi Anda. The +loadmetode pada kelas dengan demikian disusun akan dieksekusi ketika meluncurkan aplikasi Anda, mungkin sebelum memasuki main(). Ketika dikatakan "dimuat secara dinamis", itu berarti dimuat melalui paket plugin atau panggilan ke dlopen(). Jika Anda menggunakan iOS, Anda dapat mengabaikan kasus itu.

+initializedipanggil pertama kali pesan dikirim ke kelas, tepat sebelum menangani pesan itu. Ini (jelas) hanya terjadi sekali. Jika Anda menimpa +initializedalam kategori, salah satu dari tiga hal akan terjadi:

  • implementasi kategori Anda dipanggil dan implementasi kelas tidak
  • implementasi kategori orang lain dipanggil; tidak ada yang Anda tulis
  • kategori Anda belum dimuat dan implementasinya tidak pernah dipanggil.

Inilah sebabnya mengapa Anda tidak boleh menimpa +initializedalam suatu kategori - sebenarnya cukup berbahaya untuk mencoba dan mengganti metode apa pun dalam suatu kategori karena Anda tidak pernah yakin apa yang Anda gantikan atau apakah penggantian Anda sendiri akan diganti oleh kategori lain.

BTW, masalah lain yang perlu dipertimbangkan +initializeadalah jika seseorang membuat subkelas Anda, Anda berpotensi dipanggil sekali untuk kelas Anda dan sekali untuk setiap subkelas. Jika Anda melakukan sesuatu seperti menyiapkan staticvariabel, Anda pasti ingin mencegahnya: dengan dispatch_once()atau dengan menguji self == [MyClass class].


sumber