Các hằng số trong Objective-C


1002

Tôi đang phát triển một ứng dụng Cacao và tôi đang sử dụng hằng số NSStringlàm cách lưu trữ tên chính cho sở thích của mình.

Tôi hiểu đây là một ý tưởng tốt vì nó cho phép dễ dàng thay đổi các phím nếu cần thiết.
Ngoài ra, đó là toàn bộ 'tách dữ liệu của bạn khỏi khái niệm logic của bạn'.

Dù sao, có một cách tốt để làm cho các hằng số này được xác định một lần cho toàn bộ ứng dụng?

Tôi chắc chắn rằng có một cách dễ dàng và thông minh, nhưng ngay bây giờ các lớp học của tôi chỉ xác định lại những cách họ sử dụng.


7
OOP là về việc nhóm dữ liệu của bạn với logic của bạn. Những gì bạn đề xuất chỉ là một thực hành lập trình tốt, tức là, làm cho chương trình của bạn dễ dàng thay đổi.
Raffi Khatchadourian

Câu trả lời:


1287

Bạn nên tạo một tệp tiêu đề như

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

(bạn có thể sử dụng externthay vì FOUNDATION_EXPORTnếu mã của bạn sẽ không được sử dụng trong các môi trường C / C ++ hỗn hợp hoặc trên các nền tảng khác)

Bạn có thể bao gồm tệp này trong mỗi tệp sử dụng các hằng hoặc trong tiêu đề được biên dịch trước cho dự án.

Bạn xác định các hằng số này trong tệp .m như

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

Constants.m nên được thêm vào mục tiêu của ứng dụng / khung của bạn để nó được liên kết với sản phẩm cuối cùng.

Ưu điểm của việc sử dụng hằng chuỗi thay vì #definehằng số là bạn có thể kiểm tra tính bằng bằng cách sử dụng so sánh con trỏ ( stringInstance == MyFirstConstant) nhanh hơn nhiều so với so sánh chuỗi ( [stringInstance isEqualToString:MyFirstConstant]) (và dễ đọc hơn, IMO).


67
Đối với một hằng số nguyên sẽ là: extern int const MyFirstConstant = 1;
Dan Morgan

180
Nhìn chung, câu trả lời tuyệt vời, với một cảnh báo rõ ràng: bạn KHÔNG muốn kiểm tra tính bằng chuỗi với toán tử == trong Objective-C, vì nó kiểm tra địa chỉ bộ nhớ. Luôn sử dụng -isEqualToString: cho việc này. Bạn có thể dễ dàng lấy một phiên bản khác bằng cách so sánh MyFirstConstant và [NSString chuỗiWithFormat: MyFirstConstant]. Không có giả định về trường hợp của chuỗi bạn có, ngay cả với chữ. (Trong mọi trường hợp, #define là một "tiền xử lý chỉ thị", và được thay thế trước khi biên dịch, vì vậy một trong hai cách trình biên dịch thấy một chữ chuỗi cuối cùng.)
Quinn Taylor

74
Trong trường hợp này, bạn có thể sử dụng == để kiểm tra sự bằng nhau với hằng số, nếu nó thực sự được sử dụng như một biểu tượng hằng (tức là ký hiệu MyFirstConstant thay vì một chuỗi chứa @ "MyFirstConstant" được sử dụng). Một số nguyên có thể được sử dụng thay cho một chuỗi trong trường hợp này (thực sự, đó là những gì bạn đang làm - sử dụng con trỏ làm số nguyên) nhưng sử dụng chuỗi không đổi giúp việc gỡ lỗi dễ dàng hơn một chút vì giá trị của hằng có ý nghĩa dễ đọc với con người .
Barry Wark

17
+1 cho "Constants.m nên được thêm vào mục tiêu của ứng dụng / khung của bạn để nó được liên kết với sản phẩm cuối cùng." Cứu tôi tỉnh táo. @amok, làm "Nhận thông tin" trên Constants.m và chọn tab "Mục tiêu". Hãy chắc chắn rằng nó đã được kiểm tra cho (các) mục tiêu có liên quan.
PEZ

73
@Barry: Trong ca cao, tôi đã thấy một số lớp xác định các NSStringthuộc tính của chúng copythay vì retain. Như vậy, họ có thể (và nên) nắm giữ một ví dụ khác về NSString*hằng số của bạn và so sánh địa chỉ bộ nhớ trực tiếp sẽ thất bại. Ngoài ra, tôi sẽ cho rằng bất kỳ triển khai hợp lý tối ưu -isEqualToString:nào cũng sẽ kiểm tra sự bằng nhau của con trỏ trước khi đi sâu vào sự so sánh nhân vật.
Ben Mosher

280

Cách dễ nhất:

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

Cách tốt hơn:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

Một lợi ích thứ hai là việc thay đổi giá trị của hằng số không gây ra việc xây dựng lại toàn bộ chương trình của bạn.


12
Tôi nghĩ bạn không nên thay đổi giá trị của hằng số.
ruipacheco

71
Andrew đang đề cập đến việc thay đổi giá trị của hằng số trong khi mã hóa, chứ không phải trong khi ứng dụng đang chạy.
Randall

7
Có bất kỳ giá trị gia tăng nào khi thực hiện extern NSString const * const MyConstant, nghĩa là biến nó thành một con trỏ không đổi đến một đối tượng không đổi thay vì chỉ là một con trỏ không đổi?
Hari Karam Singh

4
Điều gì xảy ra, nếu tôi sử dụng khai báo này trong tệp tiêu đề, NSString * const kNSStringConst = @ "const value" tĩnh; Sự khác biệt giữa không khai báo và init riêng biệt trong các tệp .h và .m là gì?
karim

4
@Dogweather - Nơi nào đó chỉ có trình biên dịch biết câu trả lời. IE, nếu bạn muốn đưa vào một menu về trình biên dịch nào được sử dụng để biên dịch bản dựng của một ứng dụng, bạn có thể đặt nó ở đó vì mã được biên dịch nếu không sẽ không biết. Tôi không thể nghĩ đến nhiều nơi khác. Macro chắc chắn không nên được sử dụng ở nhiều nơi. Điều gì xảy ra nếu tôi có #define MY_CONST 5 và các nơi khác #define MY_CONST_2 25. Kết quả là bạn rất có thể sẽ gặp phải lỗi biên dịch khi nó cố gắng biên dịch 5_2. Không sử dụng #define cho các hằng số. Sử dụng const cho hằng số.
ArtOfWarfare

190

Cũng có một điều cần đề cập. Nếu bạn cần một hằng số toàn cầu, bạn nên sử dụng statictừ khóa.

Thí dụ

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

Do statictừ khóa, const này không hiển thị bên ngoài tệp.


Hiệu chỉnh nhỏ bởi @QuinnTaylor : các biến tĩnh được hiển thị trong một đơn vị biên dịch . Thông thường, đây là một tệp .m (như trong ví dụ này), nhưng nó có thể cắn bạn nếu bạn khai báo nó trong một tiêu đề được bao gồm ở nơi khác, vì bạn sẽ gặp lỗi liên kết sau khi biên dịch


41
Hiệu chỉnh nhỏ: các biến tĩnh được hiển thị trong một đơn vị biên dịch . Thông thường, đây là một tệp .m (như trong ví dụ này), nhưng nó có thể cắn bạn nếu bạn khai báo nó trong một tiêu đề được bao gồm ở nơi khác, vì bạn sẽ gặp lỗi liên kết sau khi biên dịch.
Quinn Taylor

Nếu tôi không sử dụng từ khóa tĩnh, liệu kNSStringConst có khả dụng trong suốt dự án không?
Danyal Aytekin

2
Ok, chỉ cần kiểm tra ... Xcode không cung cấp tự động hoàn thành cho nó trong các tệp khác nếu bạn tắt tĩnh, nhưng tôi đã thử đặt cùng tên ở hai nơi khác nhau và tái tạo lỗi liên kết của Quinn.
Danyal Aytekin

1
tĩnh trong một tệp tiêu đề không cung cấp cho các vấn đề liên kết. Tuy nhiên, mỗi đơn vị biên dịch bao gồm tệp tiêu đề sẽ có biến tĩnh riêng, do đó bạn nhận được 100 trong số chúng nếu bạn bao gồm tiêu đề từ các tệp 100 .m.
gnasher729

@kompozer Bạn đặt phần này trong phần nào của tập tin .m?
Basil Bourque

117

Câu trả lời được chấp nhận (và chính xác) nói rằng "bạn có thể bao gồm tệp [Constants.h] này ... trong tiêu đề được biên dịch trước cho dự án."

Là một người mới, tôi gặp khó khăn khi làm điều này mà không giải thích thêm - đây là cách: Trong tệp YourAppNameHere-Prefix.pch của bạn (đây là tên mặc định cho tiêu đề được biên dịch trước trong Xcode), nhập Constants.h của bạn vào trong #ifdef __OBJC__khối .

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

Cũng lưu ý rằng các tệp Constants.h và Constants.m hoàn toàn không chứa gì khác ngoại trừ những gì được mô tả trong câu trả lời được chấp nhận. (Không có giao diện hoặc thực hiện).


Tôi đã làm điều này nhưng một số tệp ném lỗi khi biên dịch "Sử dụng mã định danh không khai báo 'CONSTANTSNAME' Nếu tôi bao gồm hằng số. Trong tệp ném lỗi, nó hoạt động, nhưng đó không phải là điều tôi muốn làm. Tôi đã dọn dẹp, tắt máy xcode và xây dựng và vẫn còn vấn đề ... có ý tưởng nào không?
J3RM

50

Tôi thường sử dụng cách được đăng bởi Barry Wark và Rahul Gupta.

Mặc dù, tôi không thích lặp lại cùng một từ trong cả tệp .h và .m. Lưu ý rằng trong ví dụ sau, dòng này gần như giống hệt nhau trong cả hai tệp:

// file.h
extern NSString* const MyConst;

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

Do đó, điều tôi thích làm là sử dụng một số máy móc tiền xử lý C. Hãy để tôi giải thích thông qua ví dụ.

Tôi có một tệp tiêu đề xác định macro 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

Cặp trong .h / .m của tôi nơi tôi muốn xác định hằng số tôi thực hiện như sau:

// 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, tôi chỉ có tất cả thông tin về các hằng số trong tệp .h.


Hmm, có một chút cảnh báo, tuy nhiên, bạn không thể sử dụng kỹ thuật này như thế này nếu tệp tiêu đề được nhập vào tiêu đề được biên dịch trước, vì nó sẽ không tải tệp .h vào tệp .m vì nó đã được biên dịch. Mặc dù có một cách - hãy xem câu trả lời của tôi (vì tôi không thể đưa mã đẹp vào bình luận.
Scott Little

Tôi không thể làm việc này. Nếu tôi đặt #define SYNTHESIZE_CONSTS trước #import "myfile.h" thì đó là NSString * ... trong cả .h và .m (Đã kiểm tra bằng cách sử dụng chế độ xem trợ lý và bộ xử lý trước). Nó ném lỗi xác định lại. Nếu tôi đặt nó sau #import "myfile.h" thì nó sẽ xuất hiện NSString * ... trong cả hai tệp. Sau đó, nó ném lỗi "Biểu tượng không xác định".
arsenius

28

Bản thân tôi có một tiêu đề dành riêng để khai báo NSStrings không đổi được sử dụng cho các tùy chọn như vậy:

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

Sau đó khai báo chúng trong tệp .m đi kèm:

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

Cách tiếp cận này đã phục vụ tôi tốt.

Chỉnh sửa: Lưu ý rằng điều này hoạt động tốt nhất nếu các chuỗi được sử dụng trong nhiều tệp. Nếu chỉ có một tệp sử dụng nó, bạn có thể thực hiện #define kNSStringConstant @"Constant NSString"trong tệp .m sử dụng chuỗi.


25

Một sửa đổi nhỏ về đề xuất của @Krizz, để nó hoạt động chính xác nếu tệp tiêu đề hằng được đưa vào PCH, điều này khá bình thường. Vì bản gốc được nhập vào PCH, nó sẽ không tải lại vào .mtệp và do đó bạn không nhận được biểu tượng nào và trình liên kết không hài lòng.

Tuy nhiên, sửa đổi sau đây cho phép nó hoạt động. Đó là một chút phức tạp, nhưng nó hoạt động.

Bạn sẽ cần 3 tệp, .htệp có định nghĩa không đổi, .htệp và.m tệp, tôi sẽ sử dụng ConstantList.h, Constants.hConstants.m, tương ứng. nội dung Constants.hđơn giản là:

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

Constants.mtập tin trông như:

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

Cuối cùng, ConstantList.htệp có các khai báo thực tế trong đó và đó là tất cả:

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

Một vài điều cần lưu ý:

  1. Tôi đã phải xác định lại macro trong .mtệp sau #undef nó cho macro được sử dụng.

  2. Tôi cũng đã phải sử dụng #include thay vì #importđể điều này hoạt động chính xác và tránh trình biên dịch nhìn thấy các giá trị được biên dịch trước đó.

  3. Điều này sẽ yêu cầu biên dịch lại PCH của bạn (và có thể là toàn bộ dự án) bất cứ khi nào bất kỳ giá trị nào bị thay đổi, đó không phải là trường hợp nếu chúng được tách (và sao chép) như bình thường.

Hy vọng rằng nó hữu ích cho một ai đó.


1
Sử dụng #include đã khắc phục sự đau đầu này cho tôi.
Ramsel

Điều này có bất kỳ mất hiệu năng / bộ nhớ khi so sánh với câu trả lời được chấp nhận?
Gyfis

Trong câu trả lời cho hiệu suất so với câu trả lời được chấp nhận, không có câu trả lời nào. Đó thực sự là điều tương tự chính xác theo quan điểm của trình biên dịch. Bạn kết thúc với các tuyên bố tương tự. Chúng sẽ hoàn toàn giống nhau nếu bạn thay thế externở trên bằng FOUNDATION_EXPORT.
Scott Little

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

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

12

Như Abizer đã nói, bạn có thể đặt nó vào tệp PCH. Một cách khác không quá bẩn là tạo một tệp đính kèm cho tất cả các khóa của bạn và sau đó đưa tệp đó vào tệp bạn đang sử dụng các khóa trong hoặc đưa nó vào PCH. Với chúng trong tệp bao gồm riêng của chúng, ít nhất cung cấp cho bạn một nơi để tìm và xác định tất cả các hằng số này.


11

Nếu bạn muốn một cái gì đó như hằng số toàn cầu; một cách nhanh chóng bẩn thỉu là đưa các khai báo liên tục vào pchtập tin.


7
Chỉnh sửa .pch thường không phải là ý tưởng tốt nhất. Bạn sẽ phải tìm một nơi để thực sự xác định biến, hầu như luôn luôn là tệp .m, vì vậy sẽ có ý nghĩa hơn khi khai báo nó trong tệp .h phù hợp. Câu trả lời được chấp nhận khi tạo một cặp Constants.h / m là một câu hỏi hay nếu bạn cần chúng trong toàn bộ dự án. Tôi thường đặt các hằng số càng xa hệ thống phân cấp càng tốt, dựa trên nơi chúng sẽ được sử dụng.
Quinn Taylor

8

Hãy thử sử dụng một phương thức lớp:

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

Tôi sử dụng nó đôi khi.


6
Một phương thức lớp không phải là hằng số. Nó có chi phí trong thời gian chạy và có thể không phải lúc nào cũng trả về cùng một đối tượng (nếu bạn thực hiện theo cách đó, nhưng bạn không nhất thiết phải thực hiện theo cách đó), có nghĩa là bạn phải sử dụng isEqualToString:để so sánh, đó là một chi phí hơn nữa trong thời gian chạy. Khi bạn muốn hằng, tạo hằng.
Peter Hosey

2
@Peter Hosey, trong khi nhận xét của bạn là đúng, chúng tôi đánh giá hiệu suất đó một lần cho mỗi LỘC trở lên bằng các ngôn ngữ "cấp cao hơn" như Ruby mà không phải lo lắng về điều đó. Tôi không nói rằng bạn không đúng, mà chỉ bình luận về cách các tiêu chuẩn khác nhau trong các "thế giới" khác nhau.
Dan Rosenstark

1
Đúng trên Ruby. Hầu hết các mã hiệu suất người dùng là khá không cần thiết cho ứng dụng thông thường.
Peter De Weese

8

Nếu bạn thích hằng số không gian tên, bạn có thể tận dụng struct, Q & A Thứ Sáu 2011-08-19: Các hằng số và hàm được đặt tên

// 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
Một điều tuyệt vời! Nhưng trong ARC, bạn sẽ cần phải thêm tiền tố vào tất cả các biến trong khai báo struct với __unsafe_unretainedvòng loại để làm cho nó hoạt động.
Cemen

7

Tôi sử dụng một lớp đơn, để tôi có thể giả định lớp và thay đổi các hằng số nếu cần thiết để kiểm tra. Lớp hằng trông như thế này:

#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

Và nó được sử dụng như thế này (lưu ý việc sử dụng tốc ký cho các hằng số c - nó tiết kiệm việc gõ [[Constants alloc] init]mỗi lần):

#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

Nếu bạn muốn gọi một cái gì đó như thế này NSString.newLine;từ mục tiêu c và bạn muốn nó là hằng số tĩnh, bạn có thể tạo một cái gì đó như thế này trong swift:

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

Và bạn có định nghĩa hằng số dễ đọc, và có sẵn trong một loại bạn chọn trong khi stile giới hạn trong bối cảnh của loại.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.