id 대신 instancetype을 사용하는 것이 유리합니까?


226

Clang instancetype은 내가 볼 수 id있는 한 -allocand를 반환 유형으로 대체 하는 키워드 를 추가합니다 init.

instancetype대신에 사용하면 이점 이 id있습니까?


10
아니요 .. alloc 및 init에는 없습니다. 이미 이와 같이 작동합니다. 인스턴스 유형의 요점은 사용자 정의 메소드에게 behavoir와 같은 alloc / init를 제공 할 수 있도록하는 것입니다.
hooleyhoop

@hooleyhoop 그들은 이런 식으로 작동하지 않습니다. 그들은 id를 반환합니다. id는 'Obj-C 객체'입니다. 그게 다야 컴파일러는 그 이외의 반환 값에 대해서는 아무것도 모릅니다.
griotspeak

5
그들은 이와 같이 작동하며 계약 및 유형 검사는 이미 init에 대해 이루어지고 있습니다. nshipster.com/instancetype
kocodude

상황이 상당히 발전했습니다. 관련 결과 유형은 비교적 새롭고 다른 변경으로 인해 곧 더 분명한 문제가 될 것입니다.
griotspeak

1
그냥 댓글을 남기고 싶은 이제 아이폰 OS 8 반환하는 데 사용 방법의 많은에 id로 대체되었습니다 instancetype심지어 init에서 NSObject. Swift와 호환되는 코드를 만들려면 다음을 사용해야합니다.instancetype
Hola Soy Edu Feliz Navidad

답변:


192

확실히 이점이 있습니다. 'id'를 사용하면 본질적으로 유형 검사가 전혀 없습니다. 인스턴스 유형을 사용하면 컴파일러와 IDE가 어떤 유형의 항목이 반환되는지 알고 코드를 더 잘 확인하고 자동 완성하는 것이 좋습니다.

물론 의미가있는 곳에서만 사용하십시오 (즉, 해당 클래스의 인스턴스를 리턴하는 메소드). id는 여전히 유용합니다.


8
alloc, init, 등을 자동으로 승진하는 instancetype컴파일러. 그것은 이점이 없다고 말하는 것이 아닙니다. 있지만, 그렇지 않습니다.
Steven Fisher

10
편리 성 생성자에게 주로 유용합니다
Catfish_Man

5
2011 년 Lion 출시와 함께 ARC를 도입하고 지원하기 위해 도입되었습니다. Cocoa에서 널리 채택되는 것을 복잡하게하는 한 가지 방법은 모든 후보 메소드가 [NameOfClass가 아닌 [self 할당]을 수행하는지 감사해야한다는 것입니다. alloc], + convenienceConstructor가 instancetype을 리턴하도록 선언하고 SomeSubClass의 인스턴스를 리턴하지 않도록 [SomeSubClass easeConstructor]를 수행하는 것이 매우 혼란 스러울 수 있습니다.
Catfish_Man

2
'id'는 컴파일러가 메소드 패밀리를 유추 할 수있는 경우에만 인스턴스 유형으로 변환됩니다. 편의 생성자 또는 다른 id 반환 메서드에는 그렇지 않습니다.
Catfish_Man

4
iOS 7에서는 Foundation의 많은 메소드가 인스턴스 유형으로 변환되었습니다. 나는 개인적 으로이 코드가 적어도 3 가지 잘못된 기존 코드를 발견하는 것을 보았습니다.
Catfish_Man

336

예, instancetype적용되는 모든 경우 에 사용하면 이점이 있습니다 . 더 자세히 설명 하겠지만,이 대담한 문장으로 시작하겠습니다 : instancetype적절할 때마다 사용하십시오 . 클래스가 같은 클래스의 인스턴스를 반환 할 때마다 사용하십시오 .

사실, 애플이 지금이 주제에 대해 말한 내용은 다음과 같습니다.

코드에서 발생 id하는 값을 리턴 값으로 대체하십시오 instancetype. 이것은 일반적으로 init메소드 및 클래스 팩토리 메소드 의 경우입니다 . 컴파일러가 "alloc", "init"또는 "new"로 시작하고 반환 유형이 idreturn instancetype인 메서드를 자동으로 변환하더라도 다른 메서드는 변환하지 않습니다. Objective-C 규칙은 instancetype모든 메소드에 대해 명시 적으로 작성 하는 것입니다.

그 길을 벗어나서 계속 나아가서 왜 그것이 좋은 생각인지 설명합시다.

먼저 일부 정의는 다음과 같습니다.

 @interface Foo:NSObject
 - (id)initWithBar:(NSInteger)bar; // initializer
 + (id)fooWithBar:(NSInteger)bar;  // class factory
 @end

클래스 팩토리의 경우 항상을 사용해야 instancetype합니다. 컴파일러는 자동으로 변환하지 않습니다 idinstancetype. 그것은 id일반적인 대상입니다. 그러나 그것을 instancetype컴파일러로 만들면 컴파일러는 메소드가 반환하는 객체 유형을 알고 있습니다.

이것은 학문적 인 문제 가 아닙니다 . 예를 들어, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]Mac OS X에서 오류가 발생합니다 ( 만 해당 ) 결과, 매개 변수 유형 또는 속성이 일치하지 않는 'writeData :'라는 여러 메소드가 발견되었습니다 . 그 이유는 NSFileHandle과 NSURLHandle이 모두를 제공하기 때문 writeData:입니다. 를 [NSFileHandle fileHandleWithStandardOutput]반환하기 때문에 id컴파일러는 어떤 클래스 writeData:가 호출 되는지 확실하지 않습니다 .

다음 중 하나를 사용하여이 문제를 해결해야합니다.

[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];

또는:

NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];

물론 더 나은 해결책은을 fileHandleWithStandardOutput반환하는 것으로 선언 하는 것 instancetype입니다. 그런 다음 캐스트 또는 할당이 필요하지 않습니다.

(전용으로 iOS에서,이 예제는 오류가 발생하지 않습니다 NSFileHandle제공하고 writeData:있다. 다른 예와 같은 존재 length반환, CGFloat에서 UILayoutSupport하지만 NSUInteger에서 NSString.)

참고 : 이것을 작성 했으므로 macOS 헤더가가 NSFileHandle아닌 을 반환하도록 수정되었습니다 id.

초기화 프로그램의 경우 더 복잡합니다. 이것을 입력하면 :

- (id)initWithBar:(NSInteger)bar

… 컴파일러는 다음과 같이 입력 한 것처럼 가장합니다.

- (instancetype)initWithBar:(NSInteger)bar

이것은 ARC에 필요했습니다. Clang 언어 확장 관련 결과 유형에 설명되어 있습니다 . 그렇기 때문에 사람들은 사용해야 할 필요는 없다고 말할 것 instancetype입니다. 이 답변의 나머지 부분은 이것을 다룹니다.

세 가지 장점이 있습니다.

  1. 명백한. 귀하의 코드는 다른 것이 아니라 말하는 것을하고 있습니다.
  2. 무늬. 당신은 존재하는 중요한 문제에 대해 좋은 습관을 쌓고 있습니다.
  3. 일관성. 코드에 대한 일관성을 설정하여 코드를 더 읽기 쉽게 만듭니다.

명백한

에서 복귀 하면 기술적 인 이점이 없다는 것은 사실입니다 . 컴파일러는 자동으로 변환하기 때문에 그러나 이것은이다 에를 . 이 기발한 말에 의존하고 있습니다. 에서를 반환 한다고 쓰는 동안 컴파일러는를 반환하는 것처럼 해석합니다 .instancetypeinitidinstancetypeinitidinstancetype

이들은 컴파일러 와 동일 합니다.

- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;

이것들은 당신의 눈과 동일하지 않습니다. 기껏해야 차이점을 무시하고 넘어가는 법을 배웁니다. 이것은 무시하는 법이 아닙니다.

무늬

init다른 방법 에는 차이가 없지만 클래스 팩토리를 정의하자마자 차이 있습니다.

이 두 가지는 동일하지 않습니다 :

+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

두 번째 형태를 원합니다. instancetype생성자의 반환 유형 으로 입력하는 데 익숙하다면 매번 올바르게 얻을 수 있습니다.

일관성

마지막으로, 당신이 그것을 모두 합치면 상상해보십시오. init함수와 클래스 팩토리 를 원합니다 .

idfor 를 사용하면 다음 init과 같은 코드가 생깁니다.

- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

그러나를 사용 instancetype하면 다음을 얻습니다.

- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

더 일관성 있고 읽기 쉽습니다. 그들은 똑같은 것을 반환하고 지금은 분명합니다.

결론

의도적으로 구형 컴파일러 용 코드를 작성하지 않는 한 instancetype적절한 경우 사용해야합니다 .

메시지를 작성하기 전에 주저해야합니다 id. 스스로에게 물어보십시오 :이 클래스의 인스턴스를 반환합니까? 그렇다면 instancetype입니다.

확실히 반환 해야하는 경우가 id있지만 instancetype훨씬 더 자주 사용할 것입니다 .


4
내가 이것을 요구 한 이래 오랜 시간이 지났고 나는 당신이 여기에 제시 한 것과 같은 자세를 취한 지 오래되었습니다. 불행히도 이것은 init의 스타일 문제로 간주되며 이전 응답에 제공된 정보가 질문에 명확하게 대답했다고 생각하기 때문에 여기에 왔습니다.
griotspeak

6
명확하게 : Catfish_Man의 대답 은 첫 번째 문장에서만 정확하다고 생각합니다 . hooleyhoop의 답변 은 첫 문장을 제외하고 는 정확합니다 . 그것은 딜레마의 지옥입니다. 나는 시간이 지남에 따라 어느 것보다 더 유용하고 올바른 것으로 보이는 것을 제공하고 싶었습니다. (이 두 가지를 모두 존중하여 결국, 이것은 그들의 답변이 쓰여졌을 때보 다 훨씬 더 분명해졌습니다.)
Steven Fisher

1
시간이 지남에 따라 그렇게 희망합니다. 애플이 캐스트없이 NSString에 새로운 NSArray를 할당하는 것과 같은 소스 호환성을 유지하는 것이 중요하다고 생각하지 않는 한 어떤 이유도 생각할 수 없습니다.
Steven Fisher

4
instancetypevs는 id실제로 스타일 결정이 아닙니다. 최근의 변화 instancetype는 우리가 '내 수업의 실례'를 의미하는 instancetype곳과 같은 장소에서 사용해야한다는 사실을 분명히 보여줍니다.-init
griotspeak

2
당신은 id를 사용하는 이유가 있다고 말했습니다. 내가 생각할 수있는 유일한 것은 간결성과 이전 버전과의 호환성입니다. 둘 다 당신의 의도를 표현하는 비용을 고려할 때, 나는 그것들이 정말로 나쁜 주장이라고 생각합니다.
Steven Fisher

10

위의 답변은이 질문을 설명하기에 충분합니다. 독자가 코딩 측면에서 이해하기 위해 예제를 추가하고 싶습니다.

ClassA

@interface ClassA : NSObject

- (id)methodA;
- (instancetype)methodB;

@end

B 종

@interface ClassB : NSObject

- (id)methodX;

@end

TestViewController.m

#import "ClassA.h"
#import "ClassB.h"

- (void)viewDidLoad {

    [[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime

    [[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}

#import "ClassB.h"를 사용하지 않으면 컴파일러 오류 만 생성됩니다. 그렇지 않으면, 컴파일러는 당신이하고있는 것을 알고 있다고 가정합니다. 예를 들어, 다음은 컴파일되지만 런타임에 충돌합니다. id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey : @ "key"];
OlDor

1

The Designated Initializer 에서 자세한 내용을 확인할 수도 있습니다.

**

인스턴스 유형

**이 키워드는 리턴 유형에만 사용될 수 있으며 리턴 유형의 수신자와 일치합니다. init 메소드는 항상 인스턴스 유형을 리턴하도록 선언되었습니다. 예를 들어 파티 유형에 대해 리턴 유형을 Party로 설정하지 않는 이유는 무엇입니까? Party 클래스가 서브 클래 싱 된 경우 문제가 될 수 있습니다. 서브 클래스는 이니셜 라이저 및 리턴 유형을 포함하여 Party의 모든 메소드를 상속합니다. 만약 서브 클래스의 인스턴스가이 초기화 메시지를 보냈다면 그것은 반환 될까요? Party 인스턴스에 대한 포인터가 아니라 서브 클래스 인스턴스에 대한 포인터입니다. 문제가 없다고 생각할 수도 있습니다. 반환 유형을 변경하기 위해 서브 클래스의 초기화 프로그램을 재정의합니다. 그러나 Objective-C에서는 동일한 선택기와 리턴 유형 (또는 인수)이 다른 두 개의 메소드를 가질 수 없습니다. 초기화 메소드가 "

신분증

** Objective-C에 인스턴스 유형이 도입되기 전에 이니셜 라이저는 id (eye-dee)를 반환합니다. 이 유형은 "객체에 대한 포인터"로 정의됩니다. (ID는 C에서 void *와 매우 비슷합니다.)이 글을 쓰는 시점에서 XCode 클래스 템플릿은 여전히 ​​상용구 코드에 추가 된 이니셜 라이저의 반환 유형으로 id를 사용합니다. instancetype과 달리 id는 단순한 반환 유형 이상으로 사용될 수 있습니다. 변수가 어떤 유형의 객체를 가리키는 지 확실하지 않을 때 id 유형의 변수 또는 메소드 매개 변수를 선언 할 수 있습니다. 빠른 열거를 사용할 때 id를 사용하여 여러 유형 또는 알 수없는 유형의 객체 배열을 반복 할 수 있습니다. id는 "객체에 대한 포인터"로 정의되어 있지 않으므로이 유형의 변수 또는 객체 매개 변수를 선언 할 때는 *를 포함하지 않습니다.

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