Objective-C의 상수


1002

Cocoa 응용 프로그램을 개발 중이며 NSString기본 설정 키 이름을 저장하는 방법으로 constant를 사용 하고 있습니다.

필요한 경우 키를 쉽게 변경할 수 있기 때문에 이것이 좋은 생각이라는 것을 알고 있습니다.
또한 그것은 '논리와 데이터를 분리하는'전체 개념입니다.

어쨌든, 전체 응용 프로그램에 대해 이러한 상수를 한 번 정의하는 좋은 방법이 있습니까?

나는 쉽고 지능적인 방법이 있다고 확신하지만 지금은 내 수업은 그들이 사용하는 것을 재정의합니다.


7
OOP는 데이터 논리 그룹화하는 것 입니다. 당신이 제안하는 것은 좋은 프로그래밍 연습, 즉 프로그램을 쉽게 변경할 수있게하는 것입니다.
Raffi Khatchadourian

답변:


1287

다음과 같은 헤더 파일을 작성해야합니다

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

( 혼합 C / C ++ 환경 또는 다른 플랫폼에서 코드를 사용하지 않는 경우 extern대신 사용할 수 있습니다 FOUNDATION_EXPORT)

상수를 사용하는 각 파일 또는 프로젝트의 사전 컴파일 된 헤더에이 파일을 포함시킬 수 있습니다.

이러한 상수는 .m 파일에서 정의합니다.

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

Constants.m은 최종 제품에 연결되도록 응용 프로그램 / 프레임 워크의 대상에 추가해야합니다.

#define'd 상수 대신 문자열 상수를 사용 하면 stringInstance == MyFirstConstant문자열 비교 ( [stringInstance isEqualToString:MyFirstConstant]) 보다 훨씬 빠른 포인터 비교 ( ) (읽기 쉬운 IMO)를 사용하여 동등성을 테스트 할 수 있다는 장점 이 있습니다 .


67
정수 상수의 경우 : extern int const MyFirstConstant = 1;
Dan Morgan

180
전반적으로 큰 대답은 눈부신 경고입니다. Objective-C에서 == 연산자를 사용하여 메모리 주소를 테스트하므로 문자열 동등성을 테스트하지 않습니다. 이를 위해 항상 -isEqualToString :을 사용하십시오. MyFirstConstant와 [NSString stringWithFormat : MyFirstConstant]를 비교하여 다른 인스턴스를 쉽게 얻을 수 있습니다. 리터럴을 사용하더라도 문자열의 인스턴스에 대해 가정하지 마십시오. (어쨌든 #define은 "전 처리기 지시어"이며 컴파일 전에 대체되므로 컴파일러는 결국 문자열 리터럴을 보게됩니다.)
Quinn Taylor

74
이 경우 상수 상수로 실제로 사용되는 경우 ==를 사용하여 상수와의 동등성을 테스트해도됩니다 (예 : @ "MyFirstConstant"를 포함하는 문자열 대신 MyFirstConstant 기호가 사용됨). 이 경우 문자열 대신 정수를 사용할 수 있습니다 (실제로 포인터를 정수로 사용하여 수행하는 작업). 상수 문자열을 사용하면 상수 값이 사람이 읽을 수있는 의미를 갖기 때문에 디버깅이 약간 쉬워집니다 .
Barry Wark 2016 년

17
"Constants.m은 최종 제품에 연결되도록 응용 프로그램 / 프레임 워크의 대상에 추가해야합니다." 내 정신을 구했습니다. @amok, Constants.m에서 "정보 입수"를하고 "대상"탭을 선택하십시오. 관련 대상이 있는지 확인하십시오.
PEZ

73
@Barry : Cocoa에서는 NSString속성 copy대신 속성 을 정의하는 많은 클래스를 보았습니다 retain. 따라서, 그들은 당신의 NSString*상수 의 다른 인스턴스를 보유하고 있을 수 있으며 직접 메모리 주소 비교에 실패합니다. 또한, 나는 합리적으로 최적의 구현 -isEqualToString:은 문자 비교의 핵심에 들어가기 전에 포인터 평등을 검사 한다고 가정합니다 .
벤 모셔

280

가장 쉬운 방법:

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

더 좋은 방법:

// Prefs.h
extern NSString * const PREFS_MY_CONSTANT;

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

두 번째 장점 중 하나는 상수 값을 변경해도 전체 프로그램이 다시 작성되지 않는다는 것입니다.


12
상수 값을 변경해서는 안된다고 생각했습니다.
ruipacheco 2009

71
Andrew는 응용 프로그램이 실행되는 동안이 아니라 코딩하는 동안 상수 값을 변경하는 것을 말합니다.
랜달

7
를 수행하는 데 추가 된 값이 있습니까? extern NSString const * const MyConstant즉, 상수 포인터가 아닌 상수 객체에 대한 상수 포인터로 만드는가?
Hari Karam Singh

4
헤더 파일에서이 선언을 사용하면 static NSString * const kNSStringConst = @ "const value"; .h와 .m 파일에서 별도로 선언하지 않고 초기화하는 것의 차이점은 무엇입니까?
karim

4
@Dogweather-컴파일러 만이 답을 알고있는 곳. IE, 응용 프로그램의 빌드를 컴파일하는 데 사용 된 컴파일러를 about 메뉴에 포함하려면 컴파일 된 코드가 알지 못하므로 배치 할 수 있습니다. 다른 곳은 많이 생각할 수 없습니다. 많은 곳에서 매크로를 사용해서는 안됩니다. #define MY_CONST 5와 다른 곳에 #define MY_CONST_2 25가 있으면 어떻게됩니까? 결과는 5_2를 컴파일하려고 할 때 컴파일러 오류로 끝날 수 있습니다. 상수에 #define을 사용하지 마십시오. 상수에 const를 사용하십시오.
ArtOfWarfare

190

언급해야 할 것이 하나 있습니다. 전역 상수가 아닌 경우 static키워드 를 사용해야 합니다.

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

때문에의 static키워드,이 CONST는 파일의 외부에 표시되지 않습니다.


@QuinnTaylor의 사소한 수정 : 정적 변수는 컴파일 단위 내에서 볼 수 있습니다 . 일반적으로 이것은 (이 예제에서와 같이) 단일 .m 파일이지만 컴파일 후 링커 오류가 발생하기 때문에 다른 곳에 포함 된 헤더에 선언하면 물릴 수 있습니다.


41
사소한 수정 : 정적 변수는 컴파일 단위 내에서 볼 수 있습니다 . 일반적으로이 예는 단일 .m 파일이지만 컴파일 후 링커 오류가 발생하기 때문에 다른 곳에 포함 된 헤더에 선언하면 물릴 수 있습니다.
Quinn Taylor

정적 키워드를 사용하지 않으면 프로젝트 전체에서 kNSStringConst를 사용할 수 있습니까?
Danyal Aytekin

2
좋아, 방금 확인했다 ... Xcode는 정적을 끄면 다른 파일에서 자동 완성 기능을 제공하지 않지만 동일한 이름을 두 개의 다른 위치에 배치하려고 시도하고 Quinn의 링커 오류를 재현했습니다.
Danyal Aytekin

1
헤더 파일의 static은 링커 문제를 일으키지 않습니다. 그러나 헤더 파일을 포함하는 각 컴파일 단위는 자체 정적 변수를 가지므로 100 .m 파일의 헤더를 포함하면 그 중 100을 얻습니다.
gnasher729

@kompozer .m 파일의 어느 부분에 배치합니까?
Basil Bourque

117

받아 들여지고 정확한 대답은 "이 [Constants.h] 파일을 프로젝트의 사전 컴파일 된 헤더에 포함시킬 수 있습니다."라고 말합니다.

초보자로서 추가 설명 없이이 작업을 수행하는 데 어려움이있었습니다. 방법은 다음과 같습니다. YourAppNameHere-Prefix.pch 파일 (Xcode에서 사전 컴파일 된 헤더의 기본 이름)에서 Constants.h #ifdef __OBJC__블록 내부로 가져옵니다 .

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

또한 Constants.h 및 Constants.m 파일에는 허용되는 답변에 설명 된 것을 제외하고는 그 안에 다른 내용이 포함되어 있지 않아야합니다. (인터페이스 또는 구현 없음).


이 작업을 수행했지만 컴파일시 일부 파일에서 오류가 발생 함 "선언되지 않은 식별자 'CONSTANTSNAME'사용 오류를 발생시키는 파일에 constant.h를 포함하면 작동하지만 작동하지는 않습니다. 정리하고 종료했습니다. 엑스 코드와 빌드하고 여전히 문제 ... 어떤 아이디어?
J3RM

50

나는 일반적으로 Barry Wark와 Rahul Gupta가 게시 한 방식을 사용하고 있습니다.

.h와 .m 파일에서 동일한 단어를 반복하는 것을 좋아하지는 않습니다. 다음 예제에서 줄은 두 파일에서 거의 동일합니다.

// file.h
extern NSString* const MyConst;

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

따라서 내가 좋아하는 것은 C 전 처리기 기계를 사용하는 것입니다. 예제를 통해 설명하겠습니다.

매크로를 정의하는 헤더 파일이 있습니다 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

상수를 정의하려는 내 .h / .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, 상수에 대한 모든 정보는 .h 파일에만 있습니다.


흠, 약간의 경고가 있지만 헤더 파일을 미리 컴파일 된 헤더로 가져 오는 경우이 기법을 사용할 수 없습니다. 이미 컴파일 된 헤더 파일은 .h 파일을 .m 파일로로드하지 않기 때문입니다. 방법이 있습니다-내 답변을 참조하십시오 (주석에 멋진 코드를 넣을 수 없기 때문에.
Scott Little

이 작업을 수행 할 수 없습니다. #import "myfile.h"앞에 #define SYNTHESIZE_CONSTS를 넣으면 .h와 .m 모두에서 NSString * ...을 수행합니다 (보조자 뷰와 전처리기를 사용하여 확인). 재정의 오류가 발생합니다. #import "myfile.h"뒤에 넣으면 두 파일에서 모두 NSString * ...을 수행합니다. 그런 다음 "정의되지 않은 기호"오류가 발생합니다.
arsenius

28

나 자신은 다음과 같이 환경 설정에 사용되는 상수 NSString을 선언하는 데 전념하는 헤더를 가지고 있습니다.

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

그런 다음 첨부 된 .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";

이 접근 방식은 저에게 도움이되었습니다.

편집 : 문자열이 여러 파일에 사용되는 경우 가장 효과적입니다. 하나의 파일 만 #define kNSStringConstant @"Constant NSString"사용하는 경우 문자열을 사용하는 .m 파일에서 수행 할 수 있습니다 .


25

상수 헤더 파일이 PCH에 포함되는 경우 @Krizz 제안이 약간 수정되어 다소 정상적입니다. 원본을 PCH로 가져 .m오기 때문에 파일 로 다시로드 하지 않으므로 심볼이 표시되지 않으며 링커가 만족스럽지 않습니다.

그러나 다음과 같이 수정하면 작동합니다. 약간 복잡하지만 작동합니다.

당신은해야합니다 3 개 , 파일을 .h상수 정의는이 파일 .h파일과 .m파일, 내가 사용하는 것을 ConstantList.h, Constants.h그리고 Constants.m각각. 내용은 다음 Constants.h과 같습니다.

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

그리고 Constants.m파일 외모가 좋아 :

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

마지막으로 ConstantList.h파일에는 실제 선언이 있으며 그게 전부입니다.

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

몇 가지 참고할 사항 :

  1. 나는에있는 매크로를 다시 정의했다 .m파일을 한 후에 #undef 사용되는 매크로를 보내고.

  2. 또한 올바르게 작동하고 이전에 미리 컴파일 된 값을 보는 컴파일러를 피하기 위해 #include대신 사용해야 했습니다 #import.

  3. 값이 변경 될 때마다 PCH (그리고 아마도 전체 프로젝트)를 다시 컴파일해야합니다. 이는 정상적으로 분리되고 복제되는 경우에는 해당되지 않습니다.

그것이 누군가에게 도움이되기를 바랍니다.


1
#include를 사용하면이 두통이 해결되었습니다.
Ramsel

수락 된 답변과 비교할 때 성능 / 메모리 손실이 있습니까?
Gyfis

허용 된 답변과 비교 한 성능에 대한 답변은 없습니다. 컴파일러의 관점에서 보면 정확히 똑같습니다. 당신은 같은 선언으로 끝납니다. extern위의 를로 바꾸면 정확히 동일 합니다 FOUNDATION_EXPORT.
Scott Little

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

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

12

Abizer가 말했듯이 PCH 파일에 넣을 수 있습니다. 더럽지 않은 또 다른 방법은 모든 키에 대한 포함 파일을 만든 다음 키를 사용하는 파일에 포함하거나 PCH에 포함시키는 것입니다. 그들 자신의 포함 파일로, 적어도 이러한 상수를 찾고 정의 할 수있는 한 곳을 제공합니다.


11

전역 상수와 같은 것을 원한다면; 더 빠른 방법은 상수 선언을 pch파일에 넣는 것 입니다.


7
.pch를 편집하는 것이 일반적으로 가장 좋은 방법은 아닙니다. 실제로 항상 .m 파일 인 변수를 정의 할 위치를 찾아야 하므로 일치하는 .h 파일에서 변수를 선언 하는 것이 더 합리적 입니다. Constants.h / m 쌍 생성에 대한 대답은 전체 프로젝트에서 필요하다면 좋은 방법입니다. 나는 일반적으로 상수를 사용할 위치에 따라 가능한 한 계층 구조 아래에 상수를 넣습니다.
Quinn Taylor

8

수업 방법을 사용해보십시오.

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

가끔 사용합니다.


6
클래스 메소드는 상수가 아닙니다. 런타임에 비용이 들며 항상 같은 객체를 반환하지는 않을 수도 있습니다 (그렇게 구현하면 반드시 그렇게 구현하지는 않았을 것입니다) isEqualToString:. 비교 에 사용해야 합니다. 런타임시 추가 비용. 상수를 원하면 상수를 만드십시오.
Peter Hosey

2
@Peter Hosey는 여러분의 의견이 맞지만 루크와 같은 "상위"언어에서 LOC 당 한 번의 성능 저하를 걱정하지 않습니다. 나는 당신이 옳지 않다고 말하는 것이 아니라, "세계"에서 표준이 어떻게 다른지에 대해 언급하는 것입니다.
Dan Rosenstark

1
루비에 맞습니다. 사람들이 코딩하는 대부분의 성능은 일반적인 앱에는 필요하지 않습니다.
Peter DeWeese

8

네임 스페이스 상수가 마음에 들면 금요일 Q & A 2011-08-19 : 네임 스페이스 상수 및 함수 struct를 활용할 수 있습니다 .

// 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
대단해! 그러나 ARC에서는 구조체 선언의 모든 변수 앞에 __unsafe_unretained한정자를 접두사로 붙여야 작동합니다.
Cemen

7

싱글 톤 클래스를 사용하므로 테스트에 필요한 경우 클래스를 조롱하고 상수를 변경할 수 있습니다. 상수 클래스는 다음과 같습니다.

#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

그리고 이것은 다음과 같이 사용됩니다 (상수 c의 속기 사용에 유의하십시오- [[Constants alloc] init]매번 타이핑을 저장 합니다).

#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;objective c에서 이와 같은 것을 호출하고 정적 상수가되기를 원한다면 다음과 같이 신속하게 만들 수 있습니다.

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

그리고 읽기 쉬운 상수 정의를 가지고 있으며 유형의 컨텍스트에 제한을 두지 않고 선택한 유형 내에서 사용할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.