Amaç-C'deki sabitler


1002

Bir Kakao uygulaması geliştiriyorum ve NSStringtercihlerime göre anahtar isimlerini saklamanın yolları olarak sürekli s kullanıyorum .

Bunun iyi bir fikir olduğunu anlıyorum çünkü gerektiğinde anahtarların kolayca değiştirilmesine izin veriyor.
Ayrıca, tüm 'verilerinizi mantığınızdan ayırın' kavramı.

Her neyse, tüm uygulama için bu sabitleri bir kez tanımlamanın iyi bir yolu var mı?

Eminim kolay ve akıllı bir yol vardır, ancak şu anda sınıflarım sadece kullandıklarını yeniden tanımlıyor.


7
OOP hakkında gruplama veri ile sizin mantık. Ne öneriyorsunuz sadece iyi bir programlama uygulaması, yani programınızın değiştirilmesini kolaylaştırmak.
Raffi Khatchadourian

Yanıtlar:


1287

Gibi bir başlık dosyası oluşturmalısınız

// Constants.h
FOUNDATION_EXPORT NSString *const MyFirstConstant;
FOUNDATION_EXPORT NSString *const MySecondConstant;
//etc.

( kodunuzun karışık C / C ++ ortamlarında veya diğer platformlarda kullanılmaması externyerine kullanabilirsiniz FOUNDATION_EXPORT)

Bu dosyayı sabitleri kullanan her dosyaya veya proje için önceden derlenmiş başlığa ekleyebilirsiniz.

Bu sabitleri .m dosyasında

// Constants.m
NSString *const MyFirstConstant = @"FirstConstant";
NSString *const MySecondConstant = @"SecondConstant";

Constants.m, nihai ürüne bağlanabilmesi için uygulamanızın / çerçevenizin hedefine eklenmelidir.

#define'D sabitleri yerine dize sabitlerini kullanmanın avantajı, stringInstance == MyFirstConstantdize karşılaştırmasından ( [stringInstance isEqualToString:MyFirstConstant]) (ve okunması daha kolay, IMO) olan işaretçi karşılaştırmasını ( ) kullanarak eşitliği test edebilmenizdir .


67
Bir tamsayı sabiti için: extern int const MyFirstConstant = 1;
Dan Morgan

180
Genel olarak, harika bir cevap, bir göze çarpan uyarı ile: bellek adresini test ettiğinden, Objective-C'deki == işleci ile dize eşitliğini test etmek istemezsiniz. Bunun için daima -isEqualToString: kullanın. MyFirstConstant ve [NSString stringWithFormat: MyFirstConstant] ile karşılaştırarak kolayca farklı bir örnek alabilirsiniz. Değişmez değerlerle bile olsa, bir dizenin örneğine sahip olduğunuzu varsaymayın. (Her durumda, #define bir "önişlemci yönergesi" dir ve derlemeden önce değiştirilir, bu nedenle derleyici her iki durumda da sonunda bir dize değişmez değeri görür.)
Quinn Taylor

74
Bu durumda, gerçekten sabit bir sembol olarak kullanılıyorsa (yani @ "MyFirstConstant" içeren bir dize yerine MyFirstConstant sembolü kullanılırsa), sabit ile eşitliği test etmek için == kullanmak uygundur. Bu durumda bir dize yerine bir tamsayı kullanılabilir (gerçekten, yaptığınız şey budur - işaretçiyi bir tamsayı olarak kullanmak), ancak sabit bir dize kullanmak, sabitin değeri insan tarafından okunabilir bir anlama sahip olduğundan hata ayıklamayı biraz daha kolay hale getirir .
Barry Wark

17
"Constants.m" için +1, nihai ürüne bağlanabilmesi için uygulamanızın / çerçevenizin hedefine eklenmelidir. Aklımı kurtardım. @amok, Constants.m'de "Bilgi al" ı yapın ve "Hedefler" sekmesini seçin. İlgili hedef (ler) için kontrol edildiğinden emin olun.
PEZ

73
@Barry: kakao, onların tanımlayan sınıfları görmüş NSStringolan özellikleri copyyerine retain. Bu nedenle, NSString*sabitinizin farklı bir örneğini tutabilirler (ve olmalıdırlar) ve doğrudan bellek adresi karşılaştırması başarısız olur. Ayrıca, makul derecede optimal herhangi bir uygulamanın -isEqualToString:, karakter karşılaştırmasının nitrit derecesine girmeden önce işaretçi eşitliğini kontrol edeceğini varsayıyorum.
Ben Mosher

280

En kolay yol:

// Prefs.h
#define PREFS_MY_CONSTANT @"prefs_my_constant"

Daha iyi yol:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

// Prefs.m
NSString * const PREFS_MY_CONSTANT = @"prefs_my_constant";

İkincisinin bir yararı, bir sabitin değerinin değiştirilmesinin tüm programınızın yeniden oluşturulmasına neden olmamasıdır.


12
Sabitlerin değerini değiştirmen gerektiğini düşündüm.
ruipacheco

71
Andrew, kodlama sırasında uygulama çalışırken değil sabitin değerini değiştirmekten bahsediyor.
Randall

7
Yapmanın herhangi bir katma değeri var mı extern NSString const * const MyConstant, yani onu sabit bir işaretçiden ziyade sabit bir nesneye sabit bir işaretçi yapmak mı?
Hari Karam Singh

4
Bu bildirimi başlık dosyasında kullanırsam, statik NSString * const kNSStringConst = @ "const value"; .H ve .m dosyalarında ayrı ayrı bildirilmemesi ile init arasındaki fark nedir?
karim

4
@Dogweather - Sadece derleyicinin cevabı bildiği bir yer. IE, hangi derleyici bir uygulama derlemek için kullanılan bir about menüsüne dahil etmek istiyorsanız, derlenmiş kod aksi takdirde zaten sahip olmaz çünkü orada yerleştirebilirsiniz. Başka pek çok yer düşünemiyorum. Makrolar kesinlikle pek çok yerde kullanılmamalıdır. #Define MY_CONST 5 ve başka bir yerde #define MY_CONST_2 25 olsaydı. Sonuç, 5_2 derlemeye çalıştığında derleyici hatasıyla sonuçlanabilmenizdir. Sabitler için #define kullanmayın. Sabitler için const kullanın.
ArtOfWarfare

190

Ayrıca söylenecek bir şey var. Global olmayan bir sabite ihtiyacınız varsa, staticanahtar kelime kullanmalısınız .

Misal

// In your *.m file
static NSString * const kNSStringConst = @"const value";

staticAnahtar kelime nedeniyle , bu sabit dosyanın dışında görünmez.


@QuinnTaylor tarafından küçük düzeltme : bir derleme birimi içinde statik değişkenler görülebilir . Genellikle, bu tek bir .m dosyasıdır (ancak bu örnekte olduğu gibi), ancak derlemeden sonra bağlayıcı hataları alacağınız için başka bir yerde bulunan bir başlıkta bildirirseniz sizi ısıtabilir.


41
Küçük düzeltme: statik değişkenler bir derleme birimi içinde görülebilir . Genellikle, bu tek bir .m dosyasıdır (ancak bu örnekte olduğu gibi), ancak derlemeden sonra bağlayıcı hataları alacağınız için başka bir yerde bulunan bir başlıkta bildirirseniz sizi ısıtabilir.
Quinn Taylor

Statik anahtar kelimesini kullanmazsam, proje boyunca kNSStringConst kullanılabilir mi?
Danyal Aytekin

2
Tamam, sadece kontrol edildi ... Statik kapalı bırakırsanız Xcode diğer dosyalarda otomatik tamamlama sağlamaz, ancak aynı adı iki farklı yere koymayı denedim ve Quinn'in linker hatalarını çoğalttım.
Danyal Aytekin

1
bir başlık dosyasındaki statik bağlayıcı sorunları vermez. Ancak, başlık dosyası da dahil olmak üzere her derleme biriminin kendi statik değişkeni olur.
gnasher729

@kompozer .m dosyasının hangi kısmına yerleştirdiniz?
Basil Bourque

117

Kabul edilen (ve doğru) cevap, "bu [Constants.h] dosyasını ... projenin önceden derlenmiş başlığına ekleyebilirsiniz."

Bir acemi olarak, bunu daha fazla açıklama yapmadan yapmakta zorlandım - işte nasıl: YourAppNameHere-Prefix.pch dosyanızda (bu, Xcode'daki önceden derlenmiş başlık için varsayılan addır), Constants.h dosyanızı blok içine alın#ifdef __OBJC__ .

#ifdef __OBJC__
  #import <UIKit/UIKit.h>
  #import <Foundation/Foundation.h>
  #import "Constants.h"
#endif

Ayrıca, Constants.h ve Constants.m dosyalarının kabul edilen yanıtta açıklananlar dışında kesinlikle başka hiçbir şey içermemesi gerektiğini unutmayın. (Arabirim veya uygulama yok).


Bunu yaptım ama bazı dosyalar derleme hatası atar "Bildirilmemiş tanımlayıcı kullanımı 'CONSTANTSNAME' Eğer hata atan dosyada constant.h eklerseniz, çalışır, ama yapmak istediğim bu değil. Temizledim, kapatma xcode ve inşa ve hala sorunlar ... herhangi bir fikir?
J3RM

50

Ben genellikle Barry Wark ve Rahul Gupta'nın yayınladığı yolu kullanıyorum.

Yine de, aynı kelimeleri hem .h hem de .m dosyasında tekrarlamayı sevmiyorum. Aşağıdaki örnekte satırın her iki dosyada da neredeyse aynı olduğunu unutmayın:

// file.h
extern NSString* const MyConst;

//file.m
NSString* const MyConst = @"Lorem ipsum";

Bu nedenle, yapmak istediğim bazı C önişlemci makineleri kullanmak. Örnek üzerinden açıklayayım.

Makro tanımlayan bir başlık dosyası var STR_CONST(name, value):

// StringConsts.h
#ifdef SYNTHESIZE_CONSTS
# define STR_CONST(name, value) NSString* const name = @ value
#else
# define STR_CONST(name, value) extern NSString* const name
#endif

Benim sabit .h tanımlamak istiyorum benim .h / .m çiftinde aşağıdakileri yaparım:

// myfile.h
#import <StringConsts.h>

STR_CONST(MyConst, "Lorem Ipsum");
STR_CONST(MyOtherConst, "Hello world");

// myfile.m
#define SYNTHESIZE_CONSTS
#import "myfile.h"

et voila, sadece .h dosyasındaki sabitler hakkındaki tüm bilgilere sahibim.


Hmm, biraz uyarı var, ancak başlık dosyası önceden derlenmiş üstbilgiye içe aktarılırsa bu tekniği kullanamazsınız, çünkü zaten derlenmiş olduğu için .h dosyasını .m dosyasına yüklemez. Yine de bir yolu var - cevabımı görün (yorumlara güzel bir kod
Scott Little

Bunu çalıştıramıyorum. #Define "myfile.h" komutundan önce #define SYNTHESIZE_CONSTS komutunu koyarsam, hem .h hem de .m'de NSString * ... yapar (Yardımcı görünüm ve önişlemci kullanılarak kontrol edilir). Yeniden tanımlama hatalarını atar. #İmport "myfile.h" ifadesinden sonra koyarsam, her iki dosyada da NSString * ... ekler. Sonra "Tanımsız sembol" hataları atar.
arsenius

28

Kendim gibi tercihler için kullanılan sabit NSStrings ilan adanmış bir başlığı var:

extern NSString * const PPRememberMusicList;
extern NSString * const PPLoadMusicAtListLoad;
extern NSString * const PPAfterPlayingMusic;
extern NSString * const PPGotoStartupAfterPlaying;

Sonra onları eşlik eden .m dosyasında bildiriniz:

NSString * const PPRememberMusicList = @"Remember Music List";
NSString * const PPLoadMusicAtListLoad = @"Load music when loading list";
NSString * const PPAfterPlayingMusic = @"After playing music";
NSString * const PPGotoStartupAfterPlaying = @"Go to startup pos. after playing";

Bu yaklaşım bana iyi hizmet etti.

Düzenleme: Dizelerin birden çok dosyada kullanılması durumunda bunun en iyi sonucu verdiğini unutmayın. Yalnızca bir dosya #define kNSStringConstant @"Constant NSString"kullanırsa, dizeyi kullanan .m dosyasında yapabilirsiniz .


25

@Krizz'in önerisinde küçük bir değişiklik yapıldığı için, sabitler başlık dosyası PCH'ye dahil edilecekse düzgün çalışır, ki bu oldukça normaldir. Orijinal PCH'ye içe aktarıldığından, .mdosyayı yeniden yüklemez ve böylece hiçbir sembol almazsınız ve bağlayıcı mutsuzdur.

Ancak, aşağıdaki değişiklik çalışmasına izin verir. Biraz kıvrık, ama işe yarıyor.

Şunlar gerekir 3 , dosyaları .hsabit tanıma sahip dosyayı .hdosyası ve .mdosyayı, ben kullanacağım ConstantList.h, Constants.hve Constants.msırasıyla. içeriği Constants.hbasitçe:

// Constants.h
#define STR_CONST(name, value) extern NSString* const name
#include "ConstantList.h"

ve Constants.mdosya şöyle görünür:

// Constants.m
#ifdef STR_CONST
    #undef STR_CONST
#endif
#define STR_CONST(name, value) NSString* const name = @ value
#include "ConstantList.h"

Son olarak, ConstantList.hdosyada gerçek bildirimler var ve hepsi bu:

// ConstantList.h
STR_CONST(kMyConstant, "Value");

Unutulmaması gereken birkaç nokta:

  1. Kullanılacak makro için ing sonra.m dosyayı makro yeniden tanımlamak zorunda kaldı . #undef

  2. Bunun #includeyerine #importdüzgün çalışması ve önceden derlenmiş değerleri görmek derleyici önlemek için de kullanmak zorunda kaldı .

  3. Bu, herhangi bir değer değiştirildiğinde PCH'nizin (ve muhtemelen tüm projenin) yeniden derlenmesini gerektirir; bu, normal olarak ayrıldıklarında (ve çoğaltıldıklarında) durum böyle değildir.

Umarım bu birisi için faydalıdır.


1
#İnclude kullanmak bu baş ağrısını benim için düzeltti.
Ramsel

Kabul edilen cevapla karşılaştırıldığında performans / hafıza kaybı var mı?
Gyfis

Kabul edilen cevapla karşılaştırıldığında performansa cevap olarak, hiçbiri yoktur. Derleyicinin bakış açısından aynı şeydir. Sonuçta aynı beyanları alıyorsunuz. Eğer externyukarıdaki ile değiştirirseniz, TAM olarak aynı olurdu FOUNDATION_EXPORT.
Scott Little

14
// Prefs.h
extern NSString * const RAHUL;

// Prefs.m
NSString * const RAHUL = @"rahul";

12

Abizer'in dediği gibi, PCH dosyasına koyabilirsiniz. Bu kadar kirli olmayan bir başka yol da tüm anahtarlarınız için bir içerme dosyası yapmak ve daha sonra bu anahtarları kullandığınız dosyaya dahil etmek veya PCH'ye dahil etmektir. Onları kendi içerme dosyalarında, bu en azından tüm bu sabitleri aramak ve tanımlamak için bir yer verir.


11

Küresel sabitler gibi bir şey istiyorsanız; hızlı ve kirli bir yol sürekli bildirimleri pchdosyaya koymaktır .


7
.Pch dosyasını düzenlemek genellikle en iyi fikir değildir. Değişkeni tanımlamak için bir yer bulmanız gerekir , neredeyse her zaman bir .m dosyası, bu nedenle eşleşen .h dosyasında bildirmek daha mantıklıdır . Bir Constants.h / m çifti oluşturmanın kabul edilen cevabı, tüm proje boyunca ihtiyacınız varsa iyi bir cevaptır. Genellikle sabitleri, nerede kullanılacağına bağlı olarak, mümkün olduğunca hiyerarşiye kadar koyarım.
Quinn Taylor

8

Bir sınıf yöntemi kullanmayı deneyin:

+(NSString*)theMainTitle
{
    return @"Hello World";
}

Bazen kullanıyorum.


6
Sınıf yöntemi sabit değildir. Çalışma zamanında bir maliyeti vardır ve her zaman aynı nesneyi döndürmeyebilir (bu şekilde uygularsanız, ancak mutlaka bu şekilde uygulamadıysanız), yani isEqualToString:karşılaştırma için kullanmanız gerekir. çalışma zamanında ek bir maliyet. Sabit istediğiniz zaman sabit yapın.
Peter Hosey

2
@Peter Hosey, yorumlarınız doğru olsa da, bu performansı her LOC için bir kez veya daha fazla Ruby gibi "üst düzey" dillerde endişelenmeden alıyoruz. Doğru olmadığını söylemiyorum, sadece standartların farklı "dünyalarda" nasıl farklı olduğu hakkında yorum yapmak istiyorum.
Dan Rosenstark

1
Ruby'de doğru. İnsanların kodladığı performansın çoğu, tipik uygulama için oldukça gereksizdir.
Peter DeWeese

8

Ad alanı sabitini beğendiyseniz, yapı ile karşılaşabilirsiniz, Cuma Soru-Cevap 2011-08-19: Adlandırılmış Sabitler ve İşlevler

// in the header
extern const struct MANotifyingArrayNotificationsStruct
{
    NSString *didAddObject;
    NSString *didChangeObject;
    NSString *didRemoveObject;
} MANotifyingArrayNotifications;

// in the implementation
const struct MANotifyingArrayNotificationsStruct MANotifyingArrayNotifications = {
    .didAddObject = @"didAddObject",
    .didChangeObject = @"didChangeObject",
    .didRemoveObject = @"didRemoveObject"
};

1
Harika bir şey! Ancak ARC altında, yapı bildirimindeki tüm değişkenlerin öneklemesini __unsafe_unretainedniteleyici ile önermeniz gerekir.
Cemen

7

Sınava alay ve test için gerekirse sabitleri değiştirmek böylece tek bir sınıf kullanın. Sabitler sınıfı şuna benzer:

#import <Foundation/Foundation.h>

@interface iCode_Framework : NSObject

@property (readonly, nonatomic) unsigned int iBufCapacity;
@property (readonly, nonatomic) unsigned int iPort;
@property (readonly, nonatomic) NSString * urlStr;

@end

#import "iCode_Framework.h"

static iCode_Framework * instance;

@implementation iCode_Framework

@dynamic iBufCapacity;
@dynamic iPort;
@dynamic urlStr;

- (unsigned int)iBufCapacity
{
    return 1024u;
};

- (unsigned int)iPort
{
    return 1978u;
};

- (NSString *)urlStr
{
    return @"localhost";
};

+ (void)initialize
{
    if (!instance) {
        instance = [[super allocWithZone:NULL] init];
    }
}

+ (id)allocWithZone:(NSZone * const)notUsed
{
    return instance;
}

@end

Ve bu şekilde kullanılır (sabitler için bir steno kullanımı not edin - [[Constants alloc] init]her seferinde yazmayı kurtarır ):

#import "iCode_FrameworkTests.h"
#import "iCode_Framework.h"

static iCode_Framework * c; // Shorthand

@implementation iCode_FrameworkTests

+ (void)initialize
{
    c  = [[iCode_Framework alloc] init]; // Used like normal class; easy to mock!
}

- (void)testSingleton
{
    STAssertNotNil(c, nil);
    STAssertEqualObjects(c, [iCode_Framework alloc], nil);
    STAssertEquals(c.iBufCapacity, 1024u, nil);
}

@end

1

NSString.newLine;Hedef c'den böyle bir şeyi çağırmak ve statik sabit olmasını istiyorsanız, hızlı bir şekilde böyle bir şey oluşturabilirsiniz:

public extension NSString {
    @objc public static let newLine = "\n"
}

Ve güzel okunabilir sabit tanımınız var ve stile tür bağlamına bağlıyken, seçtiğiniz bir türden erişilebilir.

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.