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.