Konstanta dalam Objective-C

1002

Saya sedang mengembangkan aplikasi Kakao , dan saya menggunakan konstanta NSStrings sebagai cara untuk menyimpan nama-nama kunci untuk preferensi saya.

Saya mengerti ini adalah ide yang bagus karena memungkinkan penggantian kunci yang mudah jika perlu.
Plus, itu seluruh gagasan 'memisahkan data Anda dari logika Anda'.

Lagi pula, adakah cara yang baik untuk membuat konstanta ini didefinisikan sekali untuk seluruh aplikasi?

Saya yakin ada cara yang mudah dan cerdas, tetapi saat ini kelas saya hanya mendefinisikan kembali yang mereka gunakan.

Allyn
sumber
7
OOP adalah tentang mengelompokkan data Anda dengan logika Anda. Apa yang Anda usulkan hanyalah praktik pemrograman yang bagus, yakni membuat program Anda mudah diubah.
Raffi Khatchadourian

Jawaban:

1287

Anda harus membuat file header seperti

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

(Anda dapat menggunakan externalih-alih FOUNDATION_EXPORTjika kode Anda tidak akan digunakan dalam lingkungan campuran C / C ++ atau pada platform lain)

Anda dapat memasukkan file ini di setiap file yang menggunakan konstanta atau dalam header yang telah dikompilasi sebelumnya untuk proyek.

Anda mendefinisikan konstanta ini dalam file .m seperti

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

Constants.m harus ditambahkan ke target aplikasi / kerangka kerja Anda sehingga dikaitkan dengan produk akhir.

Keuntungan menggunakan konstanta string daripada konstanta #defined adalah bahwa Anda dapat menguji kesetaraan menggunakan perbandingan pointer ( stringInstance == MyFirstConstant) yang jauh lebih cepat daripada perbandingan string ( [stringInstance isEqualToString:MyFirstConstant]) (dan lebih mudah dibaca, IMO).

Barry Wark
sumber
67
Untuk konstanta integer apakah: extern int const MyFirstConstant = 1;
Dan Morgan
180
Secara keseluruhan, jawaban yang bagus, dengan satu peringatan mencolok: Anda TIDAK ingin menguji kesetaraan string dengan operator == di Objective-C, karena ia menguji alamat memori. Selalu gunakan -isEqualToString: untuk ini. Anda dapat dengan mudah mendapatkan contoh berbeda dengan membandingkan MyFirstConstant dan [NSString stringWithFormat: MyFirstConstant]. Jangan membuat asumsi tentang instance string yang Anda miliki, bahkan dengan literal. (Bagaimanapun, #define adalah "petunjuk preprocessor", dan diganti sebelum kompilasi, jadi bagaimanapun kompiler melihat string literal pada akhirnya.)
Quinn Taylor
74
Dalam kasus ini, boleh saja menggunakan == untuk menguji kesetaraan dengan konstanta, jika itu benar-benar digunakan sebagai simbol konstan (yaitu simbol MyFirstConstant alih-alih string yang berisi @ "MyFirstConstant" digunakan). Integer dapat digunakan sebagai pengganti string dalam kasus ini (sungguh, itulah yang Anda lakukan - menggunakan pointer sebagai integer) tetapi menggunakan string konstan membuat proses debug sedikit lebih mudah karena nilai konstanta memiliki makna yang dapat dibaca manusia .
Barry Wark
17
+1 untuk "Constants.m harus ditambahkan ke target aplikasi / kerangka kerja Anda sehingga dikaitkan dengan produk akhir." Menyelamatkan kewarasan saya. @amok, lakukan "Dapatkan info" di Constants.m dan pilih tab "Target". Pastikan itu diperiksa untuk target yang relevan.
PEZ
73
@ Larry: Di Cocoa, saya telah melihat sejumlah kelas yang mendefinisikan NSStringproperti mereka copyalih - alih retain. Dengan demikian, mereka bisa (dan harus) memegang contoh berbeda dari NSString*konstanta Anda , dan perbandingan alamat memori langsung akan gagal. Juga, saya akan menganggap bahwa setiap implementasi yang cukup optimal -isEqualToString:akan memeriksa kesetaraan pointer sebelum masuk ke seluk-beluk perbandingan karakter.
Ben Mosher
280

Cara termudah:

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

Cara yang lebih baik:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Salah satu manfaat yang kedua adalah bahwa mengubah nilai konstanta tidak menyebabkan pembangunan kembali seluruh program Anda.

Andrew Grant
sumber
12
Saya pikir Anda tidak seharusnya mengubah nilai konstanta.
ruipacheco
71
Andrew mengacu pada mengubah nilai konstanta saat pengkodean, bukan saat aplikasi sedang berjalan.
Randall
7
Apakah ada nilai tambah dalam melakukan extern NSString const * const MyConstant, yaitu membuatnya menjadi pointer konstan ke objek konstan, bukan hanya pointer konstan?
Hari Karam Singh
4
Apa yang terjadi, jika saya menggunakan deklarasi ini di file header, static NSString * const kNSStringConst = @ "const value"; Apa perbedaan antara tidak mendeklarasikan dan init secara terpisah dalam file .h dan .m?
karim
4
@Dogweather - Suatu tempat di mana hanya kompiler yang tahu jawabannya. Yaitu, jika Anda ingin memasukkan dalam menu tentang kompiler mana yang digunakan untuk mengkompilasi pembuatan suatu aplikasi, Anda dapat menempatkannya di sana karena kode yang dikompilasi jika tidak tidak akan mengetahui. Saya tidak bisa memikirkan banyak tempat lain. Macro tentu tidak boleh digunakan di banyak tempat. Bagaimana jika saya memiliki #define MY_CONST 5 dan di tempat lain #define MY_CONST_2 25. Hasilnya adalah Anda mungkin berakhir dengan kesalahan kompiler ketika mencoba mengkompilasi 5_2. Jangan gunakan #define untuk konstanta. Gunakan konstanta untuk konstanta.
ArtOfWarfare
190

Ada juga satu hal yang perlu disebutkan. Jika Anda membutuhkan konstanta non global, Anda harus menggunakan statickata kunci.

Contoh

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

Karena statickata kunci, const ini tidak terlihat di luar file.


Koreksi kecil oleh @QuinnTaylor : variabel statis terlihat di dalam unit kompilasi . Biasanya, ini adalah file .m tunggal (seperti dalam contoh ini), tetapi ini dapat menggigit Anda jika Anda mendeklarasikannya di header yang disertakan di tempat lain, karena Anda akan mendapatkan kesalahan tautan setelah kompilasi

kompozer
sumber
41
Koreksi kecil: variabel statis terlihat di dalam unit kompilasi . Biasanya, ini adalah file .m tunggal (seperti dalam contoh ini), tetapi ini dapat menggigit Anda jika Anda mendeklarasikannya dalam header yang disertakan di tempat lain, karena Anda akan mendapatkan kesalahan tautan setelah kompilasi.
Quinn Taylor
Jika saya tidak menggunakan kata kunci statis, apakah kNSStringConst akan tersedia di seluruh proyek?
Danyal Aytekin
2
Oke, baru saja diperiksa ... Xcode tidak menyediakan pelengkapan otomatis untuknya di file lain jika Anda mematikannya, tetapi saya mencoba meletakkan nama yang sama di dua tempat yang berbeda, dan mereproduksi kesalahan linker Quinn.
Danyal Aytekin
1
statis dalam file header tidak memberikan masalah tautan. Namun, setiap unit kompilasi termasuk file header akan mendapatkan variabel statisnya sendiri, jadi Anda mendapatkannya 100 jika Anda menyertakan header dari 100 file .m.
gnasher729
@kompozer Di bagian mana dari file .m Anda menempatkan ini?
Basil Bourque
117

Jawaban yang diterima (dan benar) mengatakan bahwa "Anda dapat memasukkan file [Constants.h] ini ... di header yang sudah dikompilasi untuk proyek."

Sebagai seorang pemula, saya mengalami kesulitan melakukan ini tanpa penjelasan lebih lanjut - inilah caranya: Di file YourAppNameHere-Prefix.pch Anda (ini adalah nama default untuk header yang dikompilasi sebelumnya dalam Xcode), impor Konstanta Anda. H di dalam #ifdef __OBJC__blok .

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

Juga perhatikan bahwa file Constants.h dan Constants.m harus benar-benar tidak mengandung apa pun di dalamnya kecuali apa yang dijelaskan dalam jawaban yang diterima. (Tidak ada antarmuka atau implementasi).

Victor Van Hee
sumber
Saya melakukan ini tetapi beberapa file melempar kesalahan pada kompilasi "Penggunaan pengidentifikasi yang tidak dideklarasikan 'CONSTANTSNAME' Jika saya menyertakan konstanta. xcode dan build dan masih bermasalah ... ada ide?
J3RM
50

Saya biasanya menggunakan cara yang diposting oleh Barry Wark dan Rahul Gupta.

Meskipun, saya tidak suka mengulang kata-kata yang sama di file .h dan .m. Perhatikan, bahwa dalam contoh berikut ini, baris hampir identik di kedua file:

// file.h
extern NSString* const MyConst;

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

Oleh karena itu, yang ingin saya lakukan adalah menggunakan beberapa mesin preprosesor C. Biarkan saya jelaskan melalui contoh.

Saya memiliki file header yang mendefinisikan makro 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

Pasangan saya .h / .m di mana saya ingin mendefinisikan konstanta saya melakukan hal berikut:

// 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, saya memiliki semua informasi tentang konstanta dalam file .h saja.

Krizz
sumber
Hmm, ada sedikit peringatan namun, Anda tidak dapat menggunakan teknik ini seperti ini jika file header diimpor ke header yang dikompilasi, karena itu tidak akan memuat file .h ke dalam file .m karena sudah dikompilasi. Ada cara - lihat jawaban saya (karena saya tidak bisa memasukkan kode yang bagus dalam komentar.
Scott Little
Saya tidak bisa mendapatkan ini berfungsi. Jika saya meletakkan #define SYNTHESIZE_CONSTS sebelum #import "myfile.h" itu memang NSString * ... di .h dan .m (Diperiksa menggunakan tampilan asisten dan preprosesor). Itu melempar kesalahan redefinisi. Jika saya meletakkannya setelah #import "myfile.h" itu tidak eksternal NSString * ... di kedua file. Lalu ia melempar kesalahan "Undefined symbol".
arsenius
28

Saya sendiri memiliki header yang didedikasikan untuk mendeklarasikan NSStrings konstan yang digunakan untuk preferensi seperti:

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

Kemudian mendeklarasikannya dalam file .m yang menyertainya:

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";

Pendekatan ini sangat membantu saya.

Sunting: Perhatikan bahwa ini berfungsi paling baik jika string digunakan dalam banyak file. Jika hanya satu file yang menggunakannya, Anda bisa melakukannya #define kNSStringConstant @"Constant NSString"di file .m yang menggunakan string.

MaddTheSane
sumber
25

Sedikit modifikasi dari saran @Krizz, sehingga berfungsi dengan baik jika file header konstanta dimasukkan dalam PCH, yang agak normal. Karena aslinya diimpor ke PCH, itu tidak akan memuatnya kembali ke dalam .mfile dan dengan demikian Anda tidak mendapatkan simbol dan linker tidak senang.

Namun, modifikasi berikut memungkinkannya berfungsi. Agak berbelit-belit, tetapi berhasil.

Anda harus 3 file, .hberkas yang memiliki definisi konstan, .hfile dan .mberkas, saya akan menggunakan ConstantList.h, Constants.hdan Constants.mmasing-masing. isinya Constants.hsederhana:

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

dan Constants.mfile tersebut terlihat seperti:

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

Akhirnya, ConstantList.hfile tersebut memiliki deklarasi aktual di dalamnya dan itu saja:

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

Beberapa hal yang perlu diperhatikan:

  1. Saya harus mendefinisikan ulang makro dalam .mfile setelah #undef itu untuk makro yang akan digunakan.

  2. Saya juga harus menggunakan #includealih-alih #importagar ini berfungsi dengan baik dan menghindari kompilator melihat nilai yang sebelumnya dikompilasi.

  3. Ini akan membutuhkan kompilasi ulang PCH Anda (dan mungkin seluruh proyek) setiap kali ada nilai yang berubah, yang tidak terjadi jika mereka dipisahkan (dan digandakan) seperti biasa.

Semoga bermanfaat bagi seseorang.

Scott Little
sumber
1
Menggunakan #include memperbaiki sakit kepala ini untuk saya.
Ramsel
Apakah ini memiliki kehilangan kinerja / memori bila dibandingkan dengan jawaban yang diterima?
Gyfis
Sebagai jawaban atas kinerja dibandingkan dengan jawaban yang diterima, tidak ada. Secara efektif hal yang sama persis dari sudut pandang kompiler. Anda berakhir dengan deklarasi yang sama. Mereka akan PERSIS sama jika Anda mengganti yang di externatas dengan FOUNDATION_EXPORT.
Scott Little
14
// Prefs.h
extern NSString * const RAHUL;

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

Seperti kata Abizer, Anda bisa memasukkannya ke file PCH. Cara lain yang tidak begitu kotor adalah dengan membuat file include untuk semua kunci Anda dan kemudian memasukkannya ke dalam file yang Anda gunakan kunci tersebut, atau, memasukkannya ke dalam PCH. Dengan mereka di file sertakan mereka sendiri, yang setidaknya memberi Anda satu tempat untuk mencari dan mendefinisikan semua konstanta ini.

Grant Limberg
sumber
11

Jika Anda menginginkan sesuatu seperti konstanta global; cara cepat yang kotor adalah dengan meletakkan deklarasi konstan ke dalam pchfile.

Abizern
sumber
7
Mengedit .pch biasanya bukan ide terbaik. Anda harus menemukan tempat untuk benar-benar mendefinisikan variabel, hampir selalu file .m, jadi lebih masuk akal untuk mendeklarasikannya dalam file .h yang cocok. Jawaban yang diterima untuk membuat pasangan Constants.h / m adalah jawaban yang bagus jika Anda membutuhkannya di seluruh proyek. Saya biasanya menempatkan konstanta sejauh mungkin dalam hierarki, berdasarkan di mana mereka akan digunakan.
Quinn Taylor
8

Coba gunakan metode kelas:

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

Terkadang saya menggunakannya.

brengsek
sumber
6
Metode kelas bukan konstanta. Ini memiliki biaya pada waktu berjalan, dan mungkin tidak selalu mengembalikan objek yang sama (itu akan jika Anda menerapkannya seperti itu, tetapi Anda belum tentu menerapkannya dengan cara itu), yang berarti Anda harus menggunakan isEqualToString:untuk perbandingan, yang merupakan biaya lebih lanjut saat dijalankan. Saat Anda menginginkan konstanta, buat konstanta.
Peter Hosey
2
@Peter Hosey, sementara komentar Anda benar, kami menganggap kinerja itu sekali dalam setiap LOC atau lebih dalam bahasa "tingkat yang lebih tinggi" seperti Ruby tanpa perlu khawatir. Saya tidak mengatakan Anda tidak benar, tetapi hanya berkomentar tentang bagaimana standar berbeda di "dunia" yang berbeda.
Dan Rosenstark
1
Benar di Ruby. Sebagian besar kode kinerja orang tidak diperlukan untuk aplikasi biasa.
Peter DeWeese
8

Jika Anda suka namespace konstan, Anda dapat memanfaatkan struct, Friday Q&A 2011-08-19: Konstanta dan Fungsi Bernama Tingkatan

// 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"
};
onmyway133
sumber
1
Suatu hal yang hebat! Tetapi di bawah ARC Anda perlu awalan semua variabel dalam deklarasi struct dengan __unsafe_unretainedkualifikasi untuk membuatnya berfungsi.
Cemen
7

Saya menggunakan kelas singleton, sehingga saya bisa mengejek kelas dan mengubah konstanta jika perlu untuk pengujian. Kelas konstanta terlihat seperti ini:

#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

Dan ini digunakan seperti ini (perhatikan penggunaan steno untuk konstanta c - menghemat mengetik [[Constants alloc] init]setiap waktu):

#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
Howard Lovatt
sumber
1

Jika Anda ingin memanggil sesuatu seperti ini NSString.newLine;dari objektif c, dan Anda ingin itu konstan statis, Anda dapat membuat sesuatu seperti ini dengan cepat:

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

Dan Anda memiliki definisi konstan yang dapat dibaca, dan tersedia dari dalam jenis pilihan Anda sementara masih terikat ke konteks jenis.

Renetik
sumber