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.7 과 iOS 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.