Objective-C에서 -init 메소드를 개인용으로 만들 수 있습니까?


147

-initObjective-C에서 클래스 의 메소드 를 숨기거나 (비공개로)해야합니다 .

어떻게해야합니까?


3
아래의 답변 에서 볼 수 있듯이 이제이를 달성하기위한 구체적이고 깨끗하며 설명적인 기능 이 있습니다 . 구체적으로 : NS_UNAVAILABLE. 나는 일반적으로이 접근법을 사용할 것을 권장합니다. OP가 승인 된 답변을 수정하는 것을 고려할 것입니까? 여기에있는 다른 답변은 많은 유용한 세부 사항을 제공하지만 이것을 달성하는 바람직한 방법은 아닙니다.
Benjohn

다른 사람들이 아래에 언급했듯이 NS_UNAVAILABLE여전히 발신자 init가을 통해 간접적으로 호출하도록 허용합니다 new. 단순히 init반환 nil을 재정의 하면 두 경우 모두 처리됩니다.
Greg Brown

답변:


88

Smalltalk와 마찬가지로 Objective-C에는 "비공개"대 "공용"방법에 대한 개념이 없습니다. 모든 메시지는 언제든지 모든 개체에 보낼 수 있습니다.

당신이 할 수있는 일은 메소드가 호출 된 NSInternalInconsistencyException경우 throw하는 것입니다 -init.

- (id)init {
    [self release];
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:@"-init is not a valid initializer for the class Foo"
                                 userInfo:nil];
    return nil;
}

실제로 훨씬 더 나은 다른 대안은 -init가능하다면 수업에 합리적으로 행동 하도록 하는 것입니다.

싱글 톤 객체가 "확보"되도록하기 위해이 작업을 수행하는 경우 귀찮게하지 마십시오. 특히, 신경 쓰지 않는다 "재정의 +allocWithZone:, -init, -retain, -release"싱글 톤을 만드는 방법. 실제로는 항상 불필요하며 실질적인 이점없이 합병증을 추가하는 것입니다.

대신, +sharedWhatever메소드가 싱글 톤에 액세스하는 방법이 되도록 코드를 작성 하고 헤더에 싱글 톤 인스턴스를 얻는 방법으로 문서화하십시오. 그것이 대부분의 경우에 필요한 전부입니다.


2
실제로 반품이 필요합니까?
philsquared

5
예, 컴파일러를 행복하게 유지합니다. 그렇지 않으면 컴파일러는 void가 아닌 리턴을 가진 메소드에서 리턴되지 않는다고 불평 할 수 있습니다.
Chris Hanson

재미 있네요. 다른 컴파일러 버전이나 스위치일까요? (난 그냥 기본 GCC를 사용하고있어 엑스 코드 3.1 스위치)
philsquared

3
패턴을 따르기 위해 개발자를 의지하는 것은 좋은 생각이 아닙니다. 예외를 던지는 것이 낫기 때문에 다른 팀의 개발자는 그렇지 않습니다. 나는 개인적인 개념이 더 좋을 것입니다.
Nick Turner

4
"실제로 큰 이점이 없습니다" . 완전히 사실이 아닙니다. 중요한 장점은 싱글 톤 패턴 을 적용 하려는 것 입니다. 새 인스턴스를 만들도록 허용하면 API에 익숙하지 않은 개발자 는 올바른 클래스가 있지만 잘못된 인스턴스가 있기 때문에 코드를 잘못 사용 alloc하고 init기능을 수행 할 수 있습니다. 이것이 OO 의 캡슐화 원칙의 본질입니다 . 다른 클래스가 액세스하거나 액세스 할 필요가없는 것을 API에 숨 깁니다. 당신은 모든 것을 공개하지 않고 인간이 모든 것을 추적하기를 기대합니다.
Nate

345

NS_UNAVAILABLE

- (instancetype)init NS_UNAVAILABLE;

사용 불가능한 속성의 짧은 버전입니다. 그것은 macOS 10.7iOS 5 에서 처음 등장했습니다 . NSObjCRuntime.h에로 정의되어 있습니다 #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE.

ObjC 코드가 아닌 Swift 클라이언트에 대해서만 메소드비활성화 하는 버전이 있습니다.

- (instancetype)init NS_SWIFT_UNAVAILABLE;

unavailable

init 호출시 컴파일러 오류unavailable 를 생성 하려면 속성을 헤더에 추가하십시오 .

-(instancetype) init __attribute__((unavailable("init not available")));  

컴파일 시간 오류

이유가 없다면 __attribute__((unavailable)), 또는 __unavailable:

-(instancetype) __unavailable init;  

doesNotRecognizeSelector:

doesNotRecognizeSelector:NSInvalidArgumentException을 발생시키는 데 사용 합니다. "런타임 시스템은 객체가 응답하거나 전달할 수없는 aSelector 메시지를 수신 할 때마다이 메소드를 호출합니다."

- (instancetype) init {
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

NSAssert

사용 NSAssertNSInternalInconsistencyException을 던져 메시지를 표시합니다 :

- (instancetype) init {
    [self release];
    NSAssert(false,@"unavailable, use initWithBlah: instead");
    return nil;
}

raise:format:

raise:format:자신의 예외를 던지기 위해 사용하십시오 .

- (instancetype) init {
    [self release];
    [NSException raise:NSGenericException 
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
                       NSStringFromClass([self class]),
                       NSStringFromSelector(@selector(initWithStateDictionary:))];
    return nil;
}

[self release]개체가 이미 alloc먹었 기 때문에 필요합니다 . ARC를 사용할 때 컴파일러가이를 호출합니다. 어쨌든 의도적으로 실행을 중지하려고 할 때 걱정할 것이 없습니다.

objc_designated_initializer

init지정된 이니셜 라이저를 강제 로 사용하지 않으 려면 다음과 같은 속성이 있습니다.

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

다른 초기화 메소드가 myOwnInit내부적으로 호출하지 않으면 경고가 생성됩니다 . 세부 사항은 다음 Xcode 릴리스 이후 Modern Objective-C 채택에 게시 될 것 입니다.


이외의 방법에 유용 할 수 있습니다 init. 이 방법이 유효하지 않은 경우 왜 객체를 초기화합니까? 또한 예외가 발생하면 init*개발자에게 올바른 방법을 전달하는 일부 사용자 지정 메시지를 지정할 수 있지만의 경우에는 해당 옵션이 없습니다 doesNotRecognizeSelector.
Aleks N.

Aleks는 없어야합니다. :) 나는 대답을 편집했습니다.
Jano

시스템이 충돌했기 때문에 이상합니다. 나는 어떤 일이 일어나지 않도록하는 것이 낫다고 생각하지만 더 좋은 방법이 있는지 궁금합니다. 내가 원하는 것은 다른 개발자들이 그것을 호출 할 수없고 '실행 중'또는 '빌딩'할 때 컴파일러가이를 잡을 수 있도록하는 것입니다.
okysabeni

1
나는 이것을 시도하고 작동하지 않습니다 :-(id) init __attribute __ ((unavailable ( "init not available"))) {NSAssert (false, @ "Use initWithType"); nil을 돌려 준다; }
okysabeni

1
@Miraaj 컴파일러에서 지원되지 않는 것 같습니다. 이 코드는 Xcode 6에서 지원됩니다. 초기화 프로그램이 지정된 호출자를 호출하지 않으면 "편의 초기화 프로그램에서 다른 이니셜 라이저에 대한 '자신의 호출이 누락되었습니다"라는 메시지가 표시됩니다.
Jano

101

Apple은 헤더 파일에서 다음을 사용하여 초기화 생성자를 비활성화했습니다.

- (instancetype)init NS_UNAVAILABLE;

이것은 Xcode에서 컴파일러 오류로 올바르게 표시됩니다. 특히 이것은 여러 HealthKit 헤더 파일에 설정되어 있습니다 (HKUnit은 그 중 하나임).


3
[MyObject new]로 객체를 인스턴스화 할 수 있습니다.
José

11
+ (instancetype) new NS_UNAVAILABLE을 수행 할 수도 있습니다.
sonicfly

@sonicfly 그렇게했지만 프로젝트는 여전히 컴파일됩니다.
CyberMew

3

기본 -init 메소드에 대해 이야기하고 있다면 할 수 없습니다. NSObject에서 상속되며 모든 클래스는 경고없이 응답합니다.

-initMyClass와 같은 새로운 메소드를 작성하여 Matt이 제안한 개인 카테고리에 넣을 수 있습니다. 그런 다음 기본 -init 메소드를 정의하여 예외가 호출 된 경우 예외를 발생 시키거나 개인용 -initMyClass를 일부 기본값으로 호출하십시오.

사람들이 init를 숨기고 싶어하는 주된 이유 중 하나는 싱글 톤 객체 입니다. 이 경우 -init를 숨길 필요가 없으며 대신 싱글 톤 객체를 반환하십시오 (또는 아직 존재하지 않는 경우 생성하십시오).


이것은 싱글 톤에 'init'만 남겨두고 'sharedWhatever'를 통해 액세스해야한다는 문서를 사용자에게 알리는 것보다 더 나은 접근법처럼 보입니다. 사람들은 일반적으로 문제를 파악하기 위해 이미 많은 시간을 낭비 할 때까지 문서를 읽지 않습니다.
Greg Maletic


3

를 사용하여 사용할 수없는 메소드를 선언 할 수 있습니다 NS_UNAVAILABLE.

따라서 @interface 아래에이 줄을 넣을 수 있습니다

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

접두사 헤더에 매크로를 더 잘 정의하십시오.

#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;

@interface YourClass : NSObject
NO_INIT

// Your properties and messages

@end

2

"만들기"라는 의미에 따라 다릅니다. Objective-C에서는 객체에서 메소드를 호출하는 것이 해당 객체로 메시지를 보내는 것으로 더 잘 설명 될 수 있습니다. 클라이언트가 객체에 대해 주어진 메소드를 호출하는 것을 금지하는 언어는 없습니다. 헤더 파일에서 메소드를 선언하지 않는 것이 가장 좋습니다. 그럼에도 불구하고 클라이언트가 올바른 서명으로 "private"메소드를 호출하면 여전히 런타임에 실행됩니다.

즉, Objective-C에서 개인용 메소드를 작성하는 가장 일반적인 방법 은 구현 파일에서 카테고리 를 작성하고 모든 "숨겨진"메소드를 선언하는 것입니다. 이렇게해도 호출이 init실행되는 것을 막을 수는 없지만, 누군가이 작업을 시도하면 컴파일러에서 경고를 내 보냅니다.

MyClass.m

@interface MyClass (PrivateMethods)
- (NSString*) init;
@end

@implementation MyClass

- (NSString*) init
{
    // code...
}

@end

MacRumors.com에는이 주제와 관련 하여 적절한 스레드 가 있습니다 .


3
불행히도이 경우 카테고리 접근 방식이 실제로 도움이되지 않습니다. 일반적으로 클래스에 메소드가 정의되어 있지 않을 수 있다는 컴파일 타임 경고가 표시됩니다. 그러나 MyClass는 루트 클래스 중 하나에서 상속해야하며 init를 정의하므로 경고가 없습니다.
Barry Wark

2

글쎄, 왜 당신이 그것을 "비공개 / 보이지 않게"만들 수없는 문제는 init 메소드가 YourClass가 아닌 id로 (alloc이 id를 리턴하기 때문에) 보내 게 만드는 원인이다

컴파일러 (체커)의 시점에서 ID는 입력 한 모든 것에 잠재적으로 반응 할 수 있으므로 (런타임에 실제로 ID에 들어가는 것을 확인할 수는 없습니다), 아무 것도 (공개적으로 = in 헤더) 컴파일이 알 수있는 것보다 init 메소드를 사용하십시오 .init이 어디에도 없기 때문에 id가 init에 응답 할 수있는 방법이 없습니다 (소스, 모든 라이브러리 등)

따라서 사용자가 init를 전달하고 컴파일러에 의해 스매시되는 것을 금지 할 수는 없지만 init를 호출하여 사용자가 실제 인스턴스를 얻지 못하도록 할 수 있습니다

단순히 nil을 반환하고 다른 사람이 얻지 못하는 이름 (initOnce, initWithSpecial ...)을 갖는 (private / invisible) 이니셜 라이저를 갖는 init을 구현하면됩니다.

static SomeClass * SInstance = nil;

- (id)init
{
    // possibly throw smth. here
    return nil;
}

- (id)initOnce
{
    self = [super init];
    if (self) {
        return self;
    }
    return nil;
}

+ (SomeClass *) shared 
{
    if (nil == SInstance) {
        SInstance = [[SomeClass alloc] initOnce];
    }
    return SInstance;
}

참고 : 누군가가 이것을 할 수 있음

SomeClass * c = [[SomeClass alloc] initOnce];

실제로 새 인스턴스를 반환하지만 initOnce가 프로젝트의 어느 곳에서도 (헤더로) 공개적으로 선언되지 않으면 경고 (ID가 응답하지 않을 수 있음 ...)를 생성하고 어쨌든 이것을 사용하는 사람은 필요합니다. 실제 이니셜 라이저가 initOnce임을 정확히 아는 것

우리는 이것을 더 예방할 수 있지만, 필요는 없습니다


0

서브 클래스에서 메소드를 숨기기 위해 어설 션을 배치하고 예외를 발생시키는 것은 좋은 의도를 가진 함정이 있다는 것을 언급해야합니다.

Jano가 첫 번째 예제에서 설명한__unavailable 대로 사용 하는 것이 좋습니다. .

서브 클래스에서 메소드를 대체 할 수 있습니다. 이는 수퍼 클래스의 메소드가 서브 클래스에서 예외를 발생시키는 메소드를 사용하는 경우 의도 한대로 작동하지 않을 수 있음을 의미합니다. 다시 말해, 당신은 이전에 사용했던 것을 깨뜨 렸습니다. 초기화 방법에서도 마찬가지입니다. 이러한 일반적인 구현의 예는 다음과 같습니다.

- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    ...bla bla...
    return self;
}

- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
    self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
    return self;
}

서브 클래스에서 이것을 수행하면 -initWithLessParameters에 어떤 일이 발생하는지 상상해보십시오.

- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

즉, 메서드를 재정의하지 않는 한 특히 초기화 메서드에서 개인 (숨겨진) 메서드를 사용하는 경향이 있습니다. 그러나 슈퍼 클래스 구현을 항상 완벽하게 제어 할 수있는 것은 아니기 때문에 이것은 또 다른 주제입니다. (이로 인해 깊이를 사용하지는 않았지만 __attribute ((objc_designated_initializer))를 나쁜 습관으로 사용하는 것에 의문을 제기합니다.)

또한 서브 클래스에서 재정의해야하는 메소드에서 어설 션 및 예외를 사용할 수 있음을 의미합니다. Objective-C에서 추상 클래스 작성 과 같은 "추상적 인"메소드 )

그리고 + new class 메소드를 잊지 마십시오.

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