@class vs. #import

709

Menurut pemahaman saya, orang harus menggunakan deklarasi kelas-maju jika ClassA perlu menyertakan header ClassB, dan ClassB perlu menyertakan header ClassA untuk menghindari inklusi melingkar. Saya juga mengerti bahwa an #importitu sederhanaifndef sehingga suatu penyertaan hanya terjadi sekali.

Pertanyaan saya adalah ini: Kapan seseorang menggunakan #importdan kapan menggunakannya @class? Terkadang jika saya menggunakan @classdeklarasi, saya melihat peringatan kompiler umum seperti berikut ini:

warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.

Akan sangat senang untuk memahami ini, dibandingkan hanya menghapus @classdeklarasi maju dan melemparkan #importke dalam untuk membungkam peringatan yang diberikan kompiler kepada saya.

Coocoo4Cocoa
sumber
10
Penerusan deklarasi hanya memberi tahu kompiler, "Hei, saya tahu saya mendeklarasikan hal-hal yang tidak Anda kenali, tetapi ketika saya mengatakan @MyClass, saya berjanji bahwa saya akan #memasukkannya dalam implementasi".
JoeCortopassi

Jawaban:

754

Jika Anda melihat peringatan ini:

peringatan: penerima 'MyCoolClass' adalah kelas maju dan @interface yang sesuai mungkin tidak ada

Anda perlu ke #importfile, tetapi Anda bisa melakukannya di file implementasi Anda (.m), dan gunakan@class deklarasi di file header Anda.

@classtidak (biasanya) menghapus kebutuhan untuk #importfile, itu hanya memindahkan persyaratan lebih dekat ke tempat informasi berguna.

Sebagai contoh

Jika Anda mengatakan @class MyCoolClass, kompiler tahu bahwa ia mungkin melihat sesuatu seperti:

MyCoolClass *myObject;

Tidak perlu khawatir tentang apa pun selain MyCoolClasskelas yang valid, dan itu harus menyediakan ruang untuk pointer ke sana (sungguh, hanya pointer). Jadi, di tajuk Anda,@class cukup 90% dari waktu.

Namun, jika Anda perlu membuat atau mengakses myObjectanggota, Anda harus memberi tahu kompilator apa metode tersebut. Pada titik ini (mungkin dalam file implementasi Anda), Anda harus #import "MyCoolClass.h", untuk memberi tahu kompiler informasi tambahan lebih dari sekadar "ini kelas".

Ben Gottlieb
sumber
5
Jawaban yang bagus, terima kasih. Untuk referensi di masa mendatang: ini juga berkaitan dengan situasi di mana Anda @classsesuatu dalam Anda .hfile, namun lupa untuk #importdi m, cobalah untuk mengakses metode pada @classobjek ed, dan mendapatkan peringatan seperti: warning: no -X method found.
Tim
24
Sebuah kasus di mana Anda perlu #import alih-alih @class adalah jika file .h menyertakan tipe data atau definisi lain yang diperlukan untuk antarmuka kelas Anda.
Ken Aspeslagh
2
Keuntungan besar lainnya yang tidak disebutkan di sini adalah kompilasi cepat. Silakan merujuk ke jawaban Venkateshwar
MartinMoizard
@ BenGottlieb Bukankah seharusnya 'm' di "myCoolClass" menjadi huruf besar? Seperti di, "MyCoolClass"?
Basil Bourque
182

Tiga aturan sederhana:

  • Hanya #importkelas super, dan protokol yang diadopsi, dalam file header ( .hfile).
  • #importsemua kelas, dan protokol, Anda mengirim pesan ke dalam implementasi ( .mfile).
  • Teruskan deklarasi untuk yang lainnya.

Jika Anda meneruskan pernyataan dalam file implementasi, maka Anda mungkin melakukan sesuatu yang salah.

PeyloW
sumber
22
Di file header, Anda mungkin juga harus #memindahkan apa pun yang mendefinisikan protokol yang diadopsi kelas Anda.
Tyler
Apakah ada perbedaan dalam mendeklarasikan #import dalam file antarmuka h atau file implementasi?
Samuel G
Dan #import jika Anda menggunakan variabel instan dari kelas
user151019
1
@ Mark - Dicakup oleh aturan # 1, hanya akses ivars dari superclass Anda, jika itu terjadi.
PeyloW
@ Tyler mengapa tidak meneruskan deklarasi protokol?
JoeCortopassi
110

Lihatlah dokumentasi Bahasa Pemrograman Objective-C pada ADC

Di bawah bagian tentang Mendefinisikan Kelas | Class Interface menjelaskan mengapa ini dilakukan:

Arahan @class meminimalkan jumlah kode yang dilihat oleh kompiler dan linker, dan karena itu cara paling sederhana untuk memberikan deklarasi maju dari nama kelas. Menjadi sederhana, itu menghindari potensi masalah yang mungkin datang dengan mengimpor file yang masih mengimpor file lain. Misalnya, jika satu kelas mendeklarasikan variabel instance yang diketik secara statis dari kelas lain, dan dua file antarmuka mereka saling mengimpor, kelas tidak dapat dikompilasi dengan benar.

Saya harap ini membantu.

Abizern
sumber
48

Gunakan deklarasi maju dalam file header jika perlu, dan #importfile header untuk setiap kelas yang Anda gunakan dalam implementasi. Dengan kata lain, Anda selalu #importfile yang Anda gunakan dalam implementasi Anda, dan jika Anda perlu referensi kelas di file header Anda menggunakan deklarasi maju juga.

The pengecualian untuk ini adalah bahwa Anda harus #importkelas atau protokol resmi Anda mewarisi dari dalam file header (dalam hal ini Anda tidak perlu impor dalam pelaksanaannya).

Marc Charbonneau
sumber
24

Praktik yang umum adalah menggunakan @class dalam file header (tetapi Anda masih perlu #mengimpor superclass), dan #memasukkan dalam file implementasi. Ini akan menghindari inklusi melingkar, dan itu hanya berfungsi.

Steph Thirion
sumber
2
Saya pikir #import lebih baik daripada #Include karena hanya mengimpor satu instance?
Matthew Schinckel
2
Benar. Tidak tahu apakah ini tentang penyertaan melingkar, atau urutan yang salah, tapi saya keluar dari aturan itu (dengan satu impor di header, impor tidak lagi diperlukan dalam penerapan subclasse), dan segera menjadi berantakan. Intinya, ikuti aturan itu, dan kompiler akan senang.
Steph Thirion
1
Dokumen saat ini mengatakan bahwa #import"seperti arahan C #include, kecuali bahwa itu memastikan bahwa file yang sama tidak pernah dimasukkan lebih dari sekali." Jadi menurut ini #importmengurus inklusi melingkar, @classarahan tidak terlalu membantu dengan itu.
Eric
24

Keuntungan lain: kompilasi cepat

Jika Anda menyertakan file header, setiap perubahan di dalamnya menyebabkan file saat ini juga untuk dikompilasi tetapi ini tidak terjadi jika nama kelas dimasukkan sebagai @class name. Tentu saja Anda harus menyertakan header di file sumber

lubang angin
sumber
18

Pertanyaan saya adalah ini. Kapan seseorang menggunakan #import dan kapan seseorang menggunakan @class?

Jawaban sederhana: Anda #importatau #includeketika ada ketergantungan fisik. Jika tidak, Anda menggunakan deklarasi maju ( @class MONClass, struct MONStruct,@protocol MONProtocol ).

Berikut adalah beberapa contoh umum ketergantungan fisik:

  • Nilai C atau C ++ (pointer atau referensi bukan ketergantungan fisik). Jika Anda memiliki CGPointsebagai ivar atau properti, kompiler perlu melihat deklarasi CGPoint.
  • Superclass Anda.
  • Metode yang Anda gunakan.

Terkadang jika saya menggunakan deklarasi @class, saya melihat peringatan kompiler umum seperti berikut: "peringatan: receiver 'FooController' adalah kelas maju dan @interface yang sesuai mungkin tidak ada."

Compiler sebenarnya sangat toleran dalam hal ini. Ini akan meninggalkan petunjuk (seperti yang di atas), tetapi Anda dapat dengan mudah membuang tumpukan Anda jika Anda mengabaikannya dan tidak melakukannya #importdengan benar. Meskipun harus (IMO), kompiler tidak menjalankan ini. Di ARC, kompiler lebih ketat karena bertanggung jawab untuk penghitungan referensi. Apa yang terjadi adalah kompiler kembali ke default ketika menemukan metode yang tidak dikenal yang Anda panggil. Setiap nilai dan parameter pengembalian diasumsikan id. Dengan demikian, Anda harus menghapus setiap peringatan dari basis kode Anda karena ini harus dianggap sebagai ketergantungan fisik. Ini analog dengan memanggil fungsi C yang tidak dideklarasikan. Dengan C, parameter diasumsikanint .

Alasan Anda lebih suka deklarasi maju adalah bahwa Anda dapat mengurangi waktu pembuatan Anda dengan faktor karena ada ketergantungan minimal. Dengan deklarasi maju, kompiler melihat ada nama, dan dapat dengan benar mem-parsing dan mengkompilasi program tanpa melihat deklarasi kelas atau semua dependensinya ketika tidak ada ketergantungan fisik. Bangunan bersih membutuhkan waktu lebih sedikit. Bangunan tambahan membutuhkan waktu lebih sedikit. Tentu, Anda akan menghabiskan lebih banyak waktu untuk memastikan bahwa semua tajuk yang Anda butuhkan dapat dilihat oleh setiap terjemahan sebagai konsekuensinya, tetapi ini terbayar dengan waktu pembuatan yang berkurang dengan cepat (dengan asumsi proyek Anda tidak kecil).

Jika Anda menggunakan #importatau #includesebagai gantinya, Anda membuat lebih banyak pekerjaan di kompiler daripada yang diperlukan. Anda juga memperkenalkan dependensi tajuk yang rumit. Anda bisa menyamakan ini dengan algoritma brute-force. Kapan kamu#import , Anda menyeret banyak informasi yang tidak perlu, yang membutuhkan banyak memori, disk I / O, dan CPU untuk mengurai dan mengkompilasi sumber.

ObjC cukup dekat dengan ideal untuk bahasa berbasis C berkaitan dengan ketergantungan karena NSObjecttipe tidak pernah bernilai -NSObject jenis selalu referensi pointer dihitung. Jadi Anda bisa lolos dengan waktu kompilasi yang sangat cepat jika Anda menyusun dependensi program Anda dengan tepat dan meneruskan jika memungkinkan karena ada sangat sedikit ketergantungan fisik yang diperlukan. Anda juga dapat mendeklarasikan properti di ekstensi kelas untuk meminimalkan ketergantungan. Itu bonus besar untuk sistem besar - Anda akan tahu perbedaannya jika Anda pernah mengembangkan basis kode C ++ besar.

Oleh karena itu, rekomendasi saya adalah menggunakan ke depan jika memungkinkan, dan kemudian ke #importmana ada ketergantungan fisik. Jika Anda melihat peringatan atau yang menyiratkan ketergantungan fisik - perbaiki semuanya. Cara mengatasinya #importdalam file implementasi Anda.

Saat Anda membangun pustaka, Anda kemungkinan akan mengklasifikasikan beberapa antarmuka sebagai grup, dalam hal ini Anda akan #importpustaka di mana ketergantungan fisik diperkenalkan (misalnya #import <AppKit/AppKit.h>). Ini dapat memperkenalkan ketergantungan, tetapi pengelola perpustakaan sering kali dapat menangani dependensi fisik untuk Anda sesuai kebutuhan - jika mereka memperkenalkan fitur, mereka dapat meminimalkan dampak yang ditimbulkannya pada bangunan Anda.

justin
sumber
Btw usaha yang bagus untuk menjelaskan hal-hal. .tapi mereka tampaknya cukup kompleks.
Ajay Sharma
NSObject types are never values -- NSObject types are always reference counted pointers.tidak sepenuhnya benar. Blok melempar celah dalam jawaban Anda, hanya mengatakan.
Richard J. Ross III
@ RichardJ.RossIII ... dan GCC memungkinkan seseorang untuk mendeklarasikan dan menggunakan nilai, sementara dentang melarangnya. dan tentu saja, harus ada nilai di balik pointer.
justin
11

Saya melihat banyak "Lakukan dengan cara ini" tetapi saya tidak melihat jawaban untuk "Mengapa?"

Jadi: Mengapa Anda harus @class di header Anda dan #import hanya dalam implementasi Anda? Anda menggandakan pekerjaan Anda dengan harus @class dan #import sepanjang waktu. Kecuali Anda menggunakan warisan. Dalam hal ini Anda akan #mengimpor beberapa kali untuk satu kelas @. Maka Anda harus ingat untuk menghapus dari banyak file yang berbeda jika tiba-tiba Anda memutuskan tidak perlu lagi mengakses deklarasi.

Mengimpor file yang sama beberapa kali bukan masalah karena sifat #import. Kompilasi kinerja juga bukan masalah. Jika ya, kami tidak akan mengimpor # Cocoa / Cocoa.h atau sejenisnya di hampir semua file header yang kita miliki.

Bruce Goodwin
sumber
1
lihat jawaban Abizem di atas untuk contoh dari dokumentasi mengapa Anda harus melakukan ini. Pemrograman defensif untuk ketika Anda memiliki dua header kelas yang saling mengimpor dengan variabel instan dari kelas lainnya.
jackslash
7

jika kita melakukan ini

@interface Class_B : Class_A

berarti kita mewarisi Class_A ke dalam Class_B, di Class_B kita dapat mengakses semua variabel class_A.

jika kita melakukan ini

#import ....
@class Class_A
@interface Class_B

di sini kita mengatakan bahwa kita menggunakan Class_A dalam program kita, tetapi jika kita ingin menggunakan variabel Class_A di Class_B kita harus #memasukkan Class_A dalam file .m (buat objek dan gunakan fungsi dan variabelnya).

Anshuman Mishra
sumber
5

untuk info tambahan tentang dependensi file & # impor & @ kelas periksa ini:

http://qualitycoding.org/file-dependencies/ itu adalah artikel yang bagus

ringkasan artikel

impor dalam file header:

  • #import superclass yang Anda warisi, dan protokol yang Anda implementasikan.
  • Maju-nyatakan segala sesuatu yang lain (kecuali jika itu berasal dari kerangka kerja dengan header utama).
  • Cobalah untuk menghilangkan semua #import lainnya.
  • Deklarasikan protokol di header mereka sendiri untuk mengurangi ketergantungan.
  • Terlalu banyak memajukan deklarasi? Anda memiliki Kelas Besar.

impor dalam file implementasi:

  • Hilangkan cruft #import yang tidak digunakan.
  • Jika sebuah metode mendelegasikan ke objek lain dan mengembalikan apa yang didapatnya, cobalah untuk meneruskan-mendeklarasikan objek itu alih-alih #memasukkannya.
  • Jika menyertakan modul memaksa Anda untuk memasukkan level setelah tingkat dependensi berturut-turut, Anda mungkin memiliki satu set kelas yang ingin menjadi perpustakaan. Bangun sebagai pustaka terpisah dengan header utama, sehingga semuanya bisa dimasukkan sebagai satu potongan prebuilt.
  • Terlalu banyak impor? Anda memiliki Kelas Besar.
Homam
sumber
3

Ketika saya berkembang, saya hanya memiliki tiga hal dalam pikiran yang tidak pernah menyebabkan masalah bagi saya.

  1. Impor kelas super
  2. Impor kelas orang tua (ketika Anda memiliki anak dan orang tua)
  3. Impor kelas di luar proyek Anda (seperti dalam kerangka kerja dan perpustakaan)

Untuk semua kelas lain (subclass dan kelas anak di proyek saya sendiri), saya mendeklarasikannya melalui kelas maju.

Constantino Tsarouhas
sumber
3

Jika Anda mencoba mendeklarasikan variabel, atau properti di file header Anda, yang belum Anda impor, Anda akan mendapatkan kesalahan dengan mengatakan bahwa kompiler tidak mengetahui kelas ini.

Pikiran pertama Anda mungkin #importitu.
Ini dapat menyebabkan masalah dalam beberapa kasus.

Misalnya jika Anda menerapkan banyak metode-C di file header, atau struct, atau yang serupa, karena itu tidak boleh diimpor beberapa kali.

Karena itu Anda dapat memberi tahu kompiler dengan @class:

Saya tahu Anda tidak tahu kelas itu, tetapi itu ada. Ini akan diimpor atau diterapkan di tempat lain

Itu pada dasarnya memberitahu kompiler untuk tutup mulut dan kompilasi, meskipun tidak yakin apakah kelas ini akan diimplementasikan.

Anda biasanya akan menggunakan #importdalam m dan @classdi H file.

IluTov
sumber
0

Teruskan deklarasi hanya untuk mencegah compiler menampilkan kesalahan.

kompiler akan tahu bahwa ada kelas dengan nama yang telah Anda gunakan dalam file header Anda untuk menyatakan.

karthick
sumber
Bisakah Anda sedikit lebih spesifik?
Sam Spencer
0

Compiler akan mengeluh hanya jika Anda akan menggunakan kelas itu sedemikian rupa sehingga kompiler perlu mengetahui implementasinya.

Ex:

  1. Ini bisa seperti jika Anda akan mengambil kelas Anda dari itu atau
  2. Jika Anda akan memiliki objek kelas itu sebagai variabel anggota (meskipun jarang).

Itu tidak akan mengeluh jika Anda hanya akan menggunakannya sebagai pointer. Tentu saja, Anda harus #memindahkannya ke file implementasi (jika Anda membuat instance objek dari kelas itu) karena ia perlu mengetahui konten kelas untuk membuat instance objek.

CATATAN: #import tidak sama dengan #include. Ini berarti tidak ada yang disebut impor melingkar. import adalah jenis permintaan bagi kompiler untuk melihat file tertentu untuk beberapa informasi. Jika informasi itu sudah tersedia, kompiler mengabaikannya.

Coba saja ini, impor Ah di Bh dan Bh di Ah Tidak akan ada masalah atau keluhan dan itu akan berfungsi dengan baik juga.

Kapan menggunakan @class

Anda menggunakan @class hanya jika Anda bahkan tidak ingin mengimpor header di header Anda. Ini bisa menjadi kasus di mana Anda bahkan tidak peduli untuk tahu apa kelas itu nantinya. Kasus di mana Anda bahkan mungkin belum memiliki header untuk kelas itu.

Contohnya adalah Anda menulis dua pustaka. Satu kelas, sebut saja A, ada di satu perpustakaan. Pustaka ini menyertakan tajuk dari pustaka kedua. Header itu mungkin memiliki pointer A tetapi sekali lagi mungkin tidak perlu menggunakannya. Jika perpustakaan 1 belum tersedia, perpustakaan B tidak akan diblokir jika Anda menggunakan @class. Tetapi jika Anda ingin mengimpor Ah, maka kemajuan perpustakaan 2 diblokir.

Deepak GM
sumber
0

Pikirkan @class dengan mengatakan pada kompiler "percayalah, ini ada".

Pikirkan #import sebagai salin-rekat.

Anda ingin meminimalkan jumlah impor yang Anda miliki karena sejumlah alasan. Tanpa penelitian, hal pertama yang terlintas dalam pikiran adalah mengurangi waktu kompilasi.

Perhatikan bahwa ketika Anda mewarisi dari kelas, Anda tidak bisa hanya menggunakan deklarasi maju. Anda perlu mengimpor file, sehingga kelas yang Anda deklarasikan tahu bagaimana itu didefinisikan.

Brandon M
sumber
0

Ini adalah contoh skenario, di mana kita membutuhkan @class.

Pertimbangkan jika Anda ingin membuat protokol di dalam file header, yang memiliki parameter dengan tipe data dari kelas yang sama, maka Anda dapat menggunakan @class. Harap ingat bahwa Anda juga dapat mendeklarasikan protokol secara terpisah, ini hanyalah sebuah contoh.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Sujananth
sumber