@class vs #import


709

Anladığım kadarıyla, ClassA'nın bir ClassB üstbilgisi içermesi ve ClassB'nin herhangi bir dairesel kapanımdan kaçınmak için bir ClassA üstbilgisi içermesi durumunda ileri sınıf bir bildirimi kullanması gerektiğine inanıyorum. Ayrıca anın #importbasit olduğunu anlıyorum, ifndefböylece içerme yalnızca bir kez olur.

Sorularım şu: Biri #importne zaman ve ne zaman kullanıyor @class? Bazen bir @classbildirim kullanırsam , aşağıdaki gibi ortak bir derleyici uyarısı görürüm:

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

Bunu, sadece @classileri bildirimi kaldırmak #importve derleyicinin bana verdiği uyarıları susturmak için içeri atmak yerine , bunu anlamak isterdim .


10
İleri bildirimi derleyiciye "Hey, tanımadığın şeyler bildirdiğimi biliyorum, ama @MyClass dediğimde, uygulamayı # içe aktaracağım söz veriyorum".
JoeCortopassi

Yanıtlar:


754

Bu uyarıyı görürseniz:

uyarı: alıcı 'MyCoolClass' ileri bir sınıftır ve karşılık gelen @ arayüz mevcut olmayabilir

#importdosyaya ihtiyacınız vardır , ancak bunu uygulama dosyanızda (.m) yapabilirsiniz ve dosyayı@class bildirimi başlık dosyanızda kullanabilirsiniz.

@class(genellikle) #importdosyalara olan ihtiyacı ortadan kaldırmaz, yalnızca gereksinimi bilginin yararlı olduğu yere yaklaştırır.

Örneğin

Eğer derlerseniz @class MyCoolClass, derleyici bunun gibi bir şey görebileceğini bilir:

MyCoolClass *myObject;

MyCoolClassGeçerli bir sınıftan başka bir şey için endişelenmesine gerek yoktur ve ona bir işaretçi için yer ayırmalıdır (gerçekten, sadece bir işaretçi). Böylece, başlığınızda @classzamanın% 90'ı yeterlidir.

Ancak, myObjectüyelerini oluşturmanız veya bunlara erişmeniz gerekiyorsa, derleyiciye bu yöntemlerin ne olduğunu bildirmeniz gerekir. Bu noktada (muhtemelen uygulama dosyanızda), #import "MyCoolClass.h"derleyiciye "bu bir sınıf" ın ötesinde ek bilgi vermeniz gerekir .


5
Harika cevap, teşekkürler. İlerisi için: Bu da durumlar ile ilgilenen @classsenin şey .hdosyasına ama unutmayın #import.m bunun üzerine bir yöntemi erişmeye çalıştığınızda @classed nesne ve benzeri uyarılar alıyorum: warning: no -X method found.
Tim

24
@ Sınıf yerine #import kullanmanız gereken bir durum, .h dosyasının sınıfınızın arabirimi için gerekli veri türlerini veya diğer tanımları içermesidir.
Ken Aspeslagh

2
Burada bahsedilmeyen bir başka büyük avantaj hızlı derleme. Lütfen Venkateshwar
MartinMoizard

@BenGottlieb "myCoolClass" içindeki "m" harfi büyük olmamalı mı? "MyCoolClass" da olduğu gibi?
Basil Bourque

182

Üç basit kural:

  • #importBaşlık dosyalarında ( .hdosyalar) yalnızca süper sınıf ve kabul edilen protokoller .
  • #importtüm sınıflar ve protokoller, uygulamada ( .mdosya) mesaj gönderir .
  • Diğer her şey için ileriye dönük beyanlar.

Uygulama dosyalarında bildirimi iletirseniz, muhtemelen yanlış bir şey yaparsınız.


22
Başlık dosyalarında, # sınıfınızın benimsediği protokolü tanımlayan herhangi bir şeyi içe aktarmanız gerekebilir.
Tyler

H arabirim dosyasında veya m uygulama dosyasında #import bildirilmesinde bir fark var mı?
Samuel G

Ve #import sınıftan örnek değişkenleri kullanıyorsanız
user151019

1
@Mark - Kural # 1 kapsamına girer, ancak o zaman bile süper sınıfınızdan ivar'lara erişin.
PeyloW

@Tyler neden protokol beyanı ileri olmasın?
JoeCortopassi

110

ADC ile ilgili Objective-C Programlama Dili belgelerine bakın

Sınıf Tanımlama bölümü altında | Sınıf Arabirimi bunun neden yapıldığını açıklar:

@Class yönergesi derleyici ve bağlayıcı tarafından görülen kod miktarını en aza indirir ve bu nedenle bir sınıf adının ileri bir bildirimini vermenin en basit yoludur. Basit olmakla birlikte, diğer dosyaları içe aktaran dosyaları içe aktarırken ortaya çıkabilecek olası sorunları önler. Örneğin, bir sınıf başka bir sınıfın statik olarak yazılan bir örnek değişkenini bildirirse ve iki arabirim dosyası birbirini içe aktarırsa, her iki sınıf da doğru şekilde derlenemez.

Umarım bu yardımcı olur.


48

Gerekirse başlık dosyasında bir ileri bildirim #importve uygulamada kullandığınız sınıflar için başlık dosyalarını kullanın. Başka bir deyişle, her zaman #importuygulamanızda kullandığınız dosyalarsınız ve başlık dosyanızdaki bir sınıfa başvurmanız gerekiyorsa bir ileri bildirim de kullanın.

Bunun istisnası#import , üstbilgi dosyanızdan devralmakta olduğunuz bir sınıf veya resmi protokolün olması gerektiğidir (bu durumda uygulamada içe aktarmanız gerekmez).


24

Yaygın uygulama başlık dosyalarında @class (yine de #sınıfı içe aktarmanız gerekir) ve uygulama dosyalarında #import kullanmaktır. Bu, dairesel kapanımları önleyecektir ve sadece çalışır.


2
#İmport'un #Include'dan daha iyi olduğunu düşündüm, sadece bir örneği içeri aktarıyor mu?
Matthew Schinckel

2
Doğru. Dairesel kapanımlar veya yanlış sipariş ile ilgili olup olmadığını bilmiyorum, ama bu kuraldan uzak kaldım (bir başlıkta bir ithalatla, alt sınıfın uygulanmasında ithalat artık gerekli değildi) ve yakında gerçekten dağınık hale geldi. Alt satırda, bu kurala uyun ve derleyici mutlu olacak.
Steph Thirion

1
Geçerli dokümanlar demek #import"emin aynı dosya birden çok kez dahil hiçbir zaman açıkça ortaya koyuyor dışında C'nin #include direktifi gibidir." Buna göre #import, dairesel kapanımlarla ilgilenir, @classdirektifler özellikle bu konuda yardımcı olmaz.
Eric

24

Başka bir avantaj: Hızlı derleme

Bir üstbilgi dosyası eklerseniz, dosyadaki herhangi bir değişiklik geçerli dosyanın da derlenmesine neden olur, ancak sınıf adı olarak dahil edilirse durum böyle değildir @class name. Elbette başlığı kaynak dosyaya eklemeniz gerekecek


18

Benim araştırmam bu. Ne zaman #import, ne zaman @class kullanılır?

Basit cevap: Siz #importveya #includefiziksel bir bağımlılık olduğunda. Aksi takdirde, (ileri bildirimleri kullanmak @class MONClass, struct MONStruct,@protocol MONProtocol ).

İşte fiziksel bağımlılığın bazı yaygın örnekleri:

  • Herhangi bir C veya C ++ değeri (bir işaretçi veya başvuru fiziksel bir bağımlılık değildir). Bir CGPointivar veya mülkünüz varsa, derleyicinin bildirimini görmesi gerekir.CGPoint .
  • Üst sınıfınız.
  • Kullandığınız bir yöntem.

Bazen bir @class bildirimi kullanırsam, aşağıdaki gibi ortak bir derleyici uyarısı görürüm: "uyarı: alıcı 'FooController' ileri bir sınıftır ve karşılık gelen @ arabirimi olmayabilir."

Derleyici aslında bu konuda çok yumuşak. İpuçlarını bırakacaktır (yukarıdaki gibi), ancak onları yoksayar ve #importdüzgün bir şekilde yapmazsanız yığınınızı kolayca çöpe atabilirsiniz . Olmasına rağmen (IMO), derleyici bunu zorlamıyor. ARC'de, derleyici daha katıdır çünkü referans sayımından sorumludur. Ne oluyor derleyici, çağırdığınız bilinmeyen bir yöntemle karşılaştığında varsayılana geri dönüyor. Her dönüş değeri ve parametresinin olduğu varsayılır id. Bu nedenle, kod tabanlarınızdaki her uyarıyı ortadan kaldırmalısınız, çünkü bu fiziksel bağımlılık olarak düşünülmelidir. Bu, bildirilmeyen bir C işlevini çağırmaya benzer. C ile parametrelerinint .

İleri bildirimleri tercih etmenizin nedeni, derleme sürelerinizi faktörlere göre azaltabilmenizdir, çünkü minimum bağımlılık vardır. İleri bildirimlerde derleyici bir ad olduğunu görür ve fiziksel bir bağımlılık olmadığında sınıf bildirimini veya tüm bağımlılıklarını görmeden programı doğru şekilde ayrıştırıp derleyebilir. Temiz yapılar daha az zaman alır. Artımlı yapılar daha az zaman alır. Elbette, sonuç olarak ihtiyacınız olan tüm başlıkların her çeviri tarafından görülebildiğinden emin olmak için biraz daha fazla zaman harcayacaksınız, ancak bu, azaltılmış derleme sürelerinde hızlı bir şekilde işe yarıyor (projenizin küçük olmadığı varsayılarak).

Kullanırsanız #importveya #includebunun yerine, derleyiciye gereğinden çok daha fazla iş atıyorsunuz. Ayrıca karmaşık başlık bağımlılıkları da sunuyorsunuz. Bunu kaba kuvvet algoritmasına benzetebilirsiniz. Siz #import, kaynakları ayrıştırmak ve derlemek için çok fazla bellek, disk G / Ç ve CPU gerektiren tonlarca gereksiz bilgiyi sürüklersiniz.

ObjC, bağımlılık açısından C tabanlı bir dil için ideale oldukça yakındır, çünkü NSObjecttürler asla değer değildir - NSObjecttürler her zaman referans sayılan işaretçilerdir. Böylece, programınızın bağımlılıklarını uygun şekilde yapılandırırsanız ve mümkün olan her yerde ileri doğru yaparsanız inanılmaz derecede derleme sürelerinden kurtulabilirsiniz, çünkü çok az fiziksel bağımlılık gerekir. Bağımlılığı daha da azaltmak için sınıf uzantılarındaki özellikleri de bildirebilirsiniz. Bu, büyük sistemler için büyük bir bonus - büyük bir C ++ kod tabanı geliştirdiyseniz, bunun farkını bilirsiniz.

Bu nedenle, tavsiyem mümkün olduğunda ileriye ve sonra #importfiziksel bağımlılığın olduğu yere kullanmaktır . Uyarıyı veya fiziksel bağımlılığı ima eden başka bir uyarı görürseniz hepsini düzeltin. Düzeltme, #importuygulama dosyanızda yapılacaktır.

Kütüphaneler oluştururken, bazı arayüzleri bir grup olarak sınıflandırırsınız, bu durumda #importfiziksel bağımlılığın getirildiği kütüphaneyi (örn. #import <AppKit/AppKit.h>) Yaparsınız . Bu bağımlılık getirebilir, ancak kütüphane koruyucular genellikle sizin için fiziksel bağımlılıkları gerektiği gibi halledebilir - bir özellik sunarlarsa, yapılarınız üzerindeki etkisini en aza indirebilirler.


BTW şeyleri açıklamak için güzel bir çaba. ama oldukça karmaşık görünüyorlar.
Ajay Sharma

NSObject types are never values -- NSObject types are always reference counted pointers.tamamen doğru değil. Bloklar cevabınıza sadece bir boşluk bırakıyorlar.
Richard J. Ross III

@ RichardJ.RossIII… ve GCC, bir kişinin değerleri beyan etmesine ve kullanmasına izin verirken, clang onu yasaklar. ve elbette, işaretçinin arkasında bir değer olmalıdır.
justin

11

Çok fazla "Bu şekilde yap" görüyorum ama "Neden?"

Öyleyse: Neden başlığınızda @ sınıf ve #import'u yalnızca uygulamanızda kullanmalısınız? @Class ve # import'u her zaman zorlayarak işinizi ikiye katlıyorsunuz . Kalıtımdan faydalanmadığınız sürece. Bu durumda, tek bir @ sınıf için birden çok kez # içe aktarırsınız. O zaman aniden bir bildirime erişmenize gerek olmadığına karar verirseniz, birden fazla farklı dosyadan kaldırmayı hatırlamanız gerekir.

#İmport'un doğası nedeniyle aynı dosyayı birden çok kez içe aktarmak sorun olmaz. Performans derlemek de sorun değil. Öyle olsaydı, sahip olduğumuz her başlık dosyasında #importing Cocoa / Cocoa.h veya benzeri olmazdı.


1
bunu neden yapmanız gerektiğinin belgelerinden bir örnek için yukarıdaki Abizem'in cevabına bakınız. Birbirini diğer sınıfın örnek değişkenleriyle içe aktaran iki sınıf üstbilgisine sahip olduğunuz zaman için defansif programlaması.
jackslash

7

eğer bunu yaparsak

@interface Class_B : Class_A

yani Class_A'yı Class_B'ye miras alıyoruz, Class_B'de class_A'nın tüm değişkenlerine erişebiliriz.

eğer bunu yapıyorsak

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

burada programımızda Class_A kullandığımızı söylüyoruz, ancak Class_B'de Class_A değişkenlerini kullanmak istiyorsak .m dosyasında Class_A'yı içe aktarmalıyız (bir nesne yapın ve işlevini ve değişkenlerini kullanın).


5

dosya bağımlılıkları ve #import & @class hakkında ek bilgi için şuna göz atın:

http://qualitycoding.org/file-dependencies/ iyi bir makale

makalenin özeti

başlık dosyalarına içe aktarma:

  • # devralınan üst sınıfı ve uyguladığınız protokolleri içe aktarın.
  • Diğer her şeyi iletin (ana başlığa sahip bir çerçeveden gelmedikçe).
  • Diğer # ithalatları ortadan kaldırmaya çalışın.
  • Bağımlılıkları azaltmak için kendi başlıklarında protokoller bildirin.
  • Çok fazla ileri bildirim mi? Büyük bir sınıfınız var.

uygulama dosyalarında ithalat:

  • Kullanılmayan cruft #imports'u ortadan kaldırın.
  • Bir yöntem başka bir nesneye yetki verir ve geri aldığı şeyi döndürürse, #importing yerine bu nesneyi iletmeye çalışın.
  • Bir modül eklemek, sizi ardışık bağımlılıkların seviyesinden sonra seviye eklemeye zorlarsa, kütüphane olmak isteyen bir dizi sınıfınız olabilir. Bir master üstbilgiye sahip ayrı bir kütüphane olarak oluşturun, böylece her şey önceden oluşturulmuş tek bir yığın olarak getirilebilir.
  • Çok fazla # ithalat mı? Büyük bir sınıfınız var.

3

Geliştiğimde aklıma gelen ve bana asla sorun çıkarmayan üç şey var.

  1. Süper sınıfları içe aktarma
  2. Ebeveyn sınıflarını içe aktarma (çocuklarınız ve ebeveynleriniz olduğunda)
  3. Projeniz dışındaki sınıfları içe aktarın (çerçeveler ve kütüphanelerde olduğu gibi)

Diğer tüm sınıflar için (projemin kendisindeki alt sınıflar ve alt sınıflar), bunları ileri sınıf yoluyla beyan ederim.


3

Başlık dosyanızda henüz içe aktarmadığınız bir değişkeni veya bir özelliği bildirmeye çalışırsanız, derleyicinin bu sınıfı bilmediğini söyleyen bir hata alırsınız.

İlk düşünceniz muhtemelen #import.
Bu, bazı durumlarda sorunlara neden olabilir.

Örneğin, başlık dosyasında, yapılarında veya benzer bir grup C yöntemini birden çok kez içe aktarmamanız gerektiği için uygularsanız.

Bu nedenle derleyiciye şunları söyleyebilirsiniz @class:

O sınıfı bilmediğini biliyorum, ama var. Başka bir yere ithal edilecek veya uygulanacak

Temel olarak derleyiciye bu sınıfın uygulanıp uygulanmayacağından emin olmasa da kapanmasını ve derlenmesini söyler.

Genellikle kullanacağı #importiçinde .m ve @classiçinde .h dosyaları.


0

Yalnızca derleyicinin hata göstermesini önlemek için bildirimi iletin.

derleyici, başlık dosyanızda bildirmek için kullandığınız ada sahip bir sınıf olduğunu bilir.


Biraz daha spesifik olabilir misiniz?
Sam Spencer

0

Derleyici yalnızca bu sınıfı derleyicinin uygulanmasını bilmesi gereken bir şekilde kullanacaksanız şikayet edecektir.

Ör:

  1. Bu, sınıfınızı ondan türetecekseniz veya
  2. Eğer üye değişken olarak o sınıftaki bir nesneye sahip olacaksanız (nadir de olsa).

İşaretçi olarak kullanacaksanız şikayet etmeyeceksiniz. Tabii ki, bir nesneyi örneklemek için sınıf içeriğini bilmesi gerektiğinden uygulama dosyasına (o sınıfın bir nesnesini somutlaştırıyorsanız) aktarmanız gerekir.

NOT: #import, #include ile aynı değildir. Bu, dairesel içe aktarma adı verilen bir şey olmadığı anlamına gelir. import, derleyicinin bazı bilgiler için belirli bir dosyaya bakması talebidir. Bu bilgi zaten mevcutsa, derleyici bilgiyi yoksayar.

Sadece bunu deneyin, Ah içinde Bh ve Bh'yi Ah olarak alın.

@Class ne zaman kullanılır

@Class öğesini yalnızca başlığınıza bir başlık almak istemiyorsanız kullanırsınız. Bu, o sınıfın ne olacağını bilmek bile istemediğiniz bir durum olabilir. Henüz bu sınıfa ait bir başlığınızın bile bulunmadığı durumlar.

Bunun bir örneği iki kütüphane yazıyor olmanız olabilir. Bir sınıf, buna A diyelim, bir kütüphanede var. Bu kütüphane, ikinci kütüphaneden bir başlık içerir. Bu üstbilginin A işaretçisi olabilir, ancak tekrar kullanması gerekmeyebilir. Kütüphane 1 henüz mevcut değilse, @class kullanırsanız kütüphane B engellenmez. Ancak Ah'yi içe aktarmak istiyorsanız, kitaplık 2'nin ilerlemesi engellenir.


0

@Class dosyasını derleyiciye "bana güven, bu var" demesini düşün.

#İmport'u kopyala yapıştır olarak düşünün.

Birkaç nedenden dolayı aldığınız ithalat sayısını en aza indirmek istiyorsunuz. Herhangi bir araştırma yapılmadan akla ilk gelen şey derleme süresini azaltmasıdır.

Bir sınıftan miras aldığınızda, yalnızca bir ileri bildirim kullanamayacağınıza dikkat edin. Bildirdiğiniz sınıfın nasıl tanımlandığını bilmesi için dosyayı içe aktarmanız gerekir.


0

Bu @class'a ihtiyacımız olan örnek bir senaryodur.

Üstbilgi dosyasında, aynı sınıfın veri türüne sahip bir parametreye sahip bir protokol oluşturmak istiyorsanız, @class kullanabilirsiniz. Lütfen protokolleri ayrı ayrı beyan edebileceğinizi de unutmayın, bu sadece bir örnektir.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.