"현대적인"Objective-C에서 iVar를 어디에 배치해야합니까?


82

Ray Wenderlich의 "iOS6 by Tutorials"책은보다 "현대적인"Objective-C 코드를 작성하는 것에 대한 매우 멋진 장을 가지고 있습니다. 한 섹션에서 책은 클래스의 헤더에서 구현 파일로 iVars를 이동하는 방법을 설명합니다. 모든 iVar는 비공개 여야하므로 이것이 올바른 방법 인 것 같습니다.

하지만 지금까지 3 가지 방법을 찾았습니다. 모두가 다르게하고 있습니다.

1.) 중괄호 블록 안에 @implementantion 아래에 iVars를 넣으십시오 (책에서 수행되는 방법입니다).

2.) 중괄호 블록없이 iVars를 @implementantion 아래에 둡니다.

3.) @implementantion (클래스 확장) 위의 개인 인터페이스에 iVars를 넣습니다.

이 모든 솔루션이 잘 작동하는 것 같고 지금까지 내 응용 프로그램의 동작에서 차이를 발견하지 못했습니다. "올바른"방법은 없다고 생각하지만 몇 가지 자습서를 작성해야하고 코드에 대해 한 가지 방법 만 선택하고 싶습니다.

어느쪽으로 가야하나요?

편집 : 여기서는 iVars에 대해서만 이야기하고 있습니다. 속성이 아닙니다. 개체에 필요한 추가 변수 만 외부에 노출되지 않아야합니다.

코드 샘플

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

삼)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

4
"올바른"에 대한 언급은 없지만 다른 옵션은 iVars를 완전히 덤프하고 모든 것을 속성으로 만드는 것입니다 (대부분 .m 파일의 클래스 확장자 내). 일관성은 상태의 구현 세부 사항에 대해 생각하지 않아도됩니다.
Phillip Mills

1
이 질문은 세 가지 옵션을 각각 보여주는 실제 코드 예제를 보여주기 위해 질문을 업데이트하면 다른 사람들에게 정말 도움이 될 것입니다. 저는 개인적으로 옵션 1 만 사용하고 옵션 3을 봤지만 옵션 2에 익숙하지 않습니다.
rmaddy

1
고마워요 매디. 샘플 코드를 추가했습니다.
TalkingCode

4
죄송합니다. 방금 업데이트를 확인했습니다. 옵션 2는 유효하지 않습니다. 그것은 ivar를 생성하지 않습니다. 그러면 파일 전역 변수가 생성됩니다. 해당 클래스의 모든 인스턴스는 해당 한 세트의 변수를 공유합니다. 인스턴스 변수가 아닌 클래스 변수를 사용할 수 있습니다.
rmaddy

답변:


162

인스턴스 변수를 @implementation블록 또는 클래스 확장 에 넣는 기능은 모든 버전의 iOS 및 64 비트 Mac OS X 프로그램에서 사용되는 "현대적인 Objective-C 런타임"의 기능입니다.

32 비트 Mac OS X 앱을 작성하려면 @interface선언에 인스턴스 변수를 넣어야합니다 . 하지만 앱의 32 비트 버전을 지원할 필요는 없습니다. OS X은 5 년 전에 출시 된 버전 10.5 (Leopard) 이후 64 비트 앱을 지원했습니다.

따라서 최신 런타임을 사용할 앱만 작성한다고 가정 해 보겠습니다. ivar를 어디에 두어야합니까?

옵션 0 : @interface(하지 마세요)

먼저 인스턴스 변수를 선언 에 넣지 않는 이유를 살펴 보겠습니다 @interface.

  1. 인스턴스 변수를 배치 @interface하면 클래스 사용자에게 구현 세부 정보가 노출됩니다. 이로 인해 해당 사용자 (자신의 클래스를 사용하는 경우에도 자신도!)가하지 말아야 할 구현 세부 사항에 의존하게 될 수 있습니다. (이것은 우리가 ivars 선언 여부와 무관합니다 @private.)

  2. 인스턴스 변수를 삽입 @interface하면 컴파일 시간이 더 오래 걸립니다. ivar 선언을 추가, 변경 또는 제거 할 때마다 .m인터페이스를 가져 오는 모든 파일 을 다시 컴파일해야하기 때문 입니다.

따라서 인스턴스 변수를 @interface. 어디에 두어야합니까?

옵션 2 : @implementation중괄호없이 (하지 마십시오)

다음으로, 옵션 2,“중괄호 블록없이 @implementantion 아래에 iVars 배치”에 대해 논의 해 보겠습니다. 이것은 인스턴스 변수를 선언 하지 않습니다 ! 당신은 이것에 대해 이야기하고 있습니다.

@implementation Person

int age;
NSString *name;

...

이 코드는 두 개의 전역 변수를 정의합니다. 인스턴스 변수를 선언하지 않습니다.

예를 들어 모든 인스턴스가 캐시와 같은 일부 상태를 공유하기를 원하기 때문에 전역 변수가 필요한 경우 .m파일에서도 전역 변수를 정의하는 @implementation것이 좋습니다. 그러나이 옵션은 ivar를 선언하지 않기 때문에 ivar를 선언하는 데 사용할 수 없습니다. (또한, 구현에 전용 전역 변수는 일반적으로 static전역 네임 스페이스를 오염시키고 링크 타임 오류를 방지하기 위해 선언해야 합니다.)

그러면 옵션 1과 3이 남습니다.

옵션 1 : @implementation중괄호 포함 (Do It)

일반적으로 옵션 1을 사용합니다. 다음 @implementation과 같이 중괄호로 기본 블록 에 넣습니다 .

@implementation Person {
    int age;
    NSString *name;
}

우리는 그것들의 존재를 비공개로 유지하고 앞서 설명한 문제를 방지하고 일반적으로 클래스 확장에 넣을 이유가 없기 때문에 여기에 넣었습니다.

그렇다면 옵션 3을 언제 클래스 확장에 넣어야할까요?

옵션 3 : 수업 연장에서 (필요할 때만 수행)

클래스의 .NET Framework 파일과 동일한 파일의 클래스 확장에 넣을 이유가 거의 없습니다 @implementation. @implementation그런 경우에는 그냥 넣는 게 좋을 것 같습니다 .

그러나 때때로 우리는 소스 코드를 여러 파일로 나누고 싶을만큼 충분히 큰 클래스를 작성할 수 있습니다. 카테고리를 사용하여이를 수행 할 수 있습니다. 예를 들어, UICollectionView(약간 큰 클래스) 구현 하는 경우 재사용 가능한 뷰 (셀 및 보충 뷰)의 큐를 관리하는 코드를 별도의 소스 파일에 넣기로 결정할 수 있습니다. 이러한 메시지를 범주로 분리하여이를 수행 할 수 있습니다.

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

이제에서 기본 UICollectionView메서드를 UICollectionView.m구현할 수 있고에서 재사용 가능한 뷰를 관리하는 메서드를 구현할 수 있으므로 UICollectionView+ReusableViews.m소스 코드를 좀 더 쉽게 관리 할 수 ​​있습니다.

그러나 재사용 가능한 뷰 관리 코드에는 몇 가지 인스턴스 변수가 필요합니다. 이러한 변수는의 메인 클래스 @implementation에 노출되어야 UICollectionView.m하므로 컴파일러는 .o파일 에서 변수 를 내 보냅니다 . 또한 이러한 인스턴스 변수를의 코드에 노출해야 UICollectionView+ReusableViews.m해당 메서드가 ivar를 사용할 수 있습니다.

여기에 클래스 확장이 필요합니다. 재사용 가능한보기 관리 ivar를 개인 헤더 파일의 클래스 확장에 넣을 수 있습니다.

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

이 헤더 파일은 라이브러리 사용자에게 제공되지 않습니다. 이 ivar를 보는 데 필요한 모든 항목이 볼 수 있도록 에서 UICollectionView.m및에서 가져 오기만하면 됩니다. 또한 재사용 가능한 뷰 관리 코드를 초기화하기 위해 메인 메서드가 호출 할 메서드를 던졌습니다 . 우리는에서 해당 메소드를 호출 할 수 있습니다 에서 , 우리는 그것을에서 구현하는 것이다 .UICollectionView+ReusableViews.minit-[UICollectionView initWithFrame:collectionViewLayout:]UICollectionView.mUICollectionView+ReusableViews.m


4
옵션 3 : 또한 1) 자체 구현 (@property int age) 내에서도 ivar에 대한 속성 사용을 시행하고 2) 읽기 전용 공용 속성 (@property (readonly) int age)을 재정의하려는 경우에도 사용할 수 있습니다. 코드가 구현에서 readwrite로 속성에 액세스 할 수 있도록 헤더에 있습니다 (@property (readwrite) int age). 다른 방법이 없다면 @rob mayoff?
leanne

@leanne readonly속성을 비공개 로 재정의 readwrite하려면 클래스 확장을 사용해야합니다. 그러나 질문은 속성 선언을 어디에 둘지가 아니라 어디에 ivar를 둘지에 관한 것이 었습니다. 클래스 확장을 사용하여 구현이 ivar에 액세스하는 것을 방지하는 방법을 알지 못합니다. 그렇게하려면 컴파일러가 모든 ivar 선언 클래스 확장을 확인해야 @implementation.
rob mayoff

@rob mayoff, true 및 true-유효한 포인트를 만듭니다. Holli는 속성이 여기에서 문제가되지 않는다고 명시했으며, "시행"은 사용과 관련하여 제 입장에서 약간 강했습니다. 그럼에도 불구하고 속성을 사용하여 ivar에 직접 액세스하는 대신 속성을 사용하려면 그렇게하는 것이 좋습니다. 물론 '_', (_age = someAge)를 사용하여 ivar에 직접 액세스 할 수 있습니다. 나는 많은 사람들이 함께 사용하기 때문에 ivars를 논의 할 때 언급 할 가치가 있다고 생각했기 때문에 코멘트를 추가했습니다.
leanne

이것이 바로“옵션 0 : @ 인터페이스에서 (하지 마십시오)”입니다.
rob mayoff 2015-04-24

5

옵션 2는 잘못되었습니다. 그것들은 인스턴스 변수가 아니라 전역 변수입니다.

옵션 1과 3은 본질적으로 동일합니다. 그것은 전혀 차이가 없습니다.

선택 사항은 인스턴스 변수를 헤더 파일 또는 구현 파일에 넣을지 여부입니다. 헤더 파일 사용의 장점은 인스턴스 변수와 인터페이스 선언을보고 편집 할 수있는 빠르고 쉬운 키보드 단축키 (Xcode에서 Command + Control + Up)가 있다는 것입니다.

단점은 공용 헤더에 클래스의 개인 정보를 노출한다는 것입니다. 특히 다른 사람이 사용할 코드를 작성하는 경우에는 바람직하지 않습니다. 또 다른 잠재적 인 문제는 Objective-C ++를 사용하는 경우 헤더 파일에 C ++ 데이터 유형을 넣지 않는 것이 좋습니다.

구현 인스턴스 변수는 특정 상황에서 훌륭한 옵션이지만 대부분의 코드에서 Xcode에서 작업하는 코더가 더 편리하기 때문에 헤더에 인스턴스 변수를 넣습니다. 제 조언은 당신에게 더 편리하다고 느끼는 일을하는 것입니다.


4

대체로 하위 클래스에 대한 ivar의 가시성과 관련이 있습니다. 서브 클래스는 @implementation블록에 정의 된 인스턴스 변수에 액세스 할 수 없습니다 .

공개 검사를 위해 인스턴스 변수를 노출하지 않는 것을 선호하는 배포하려는 재사용 가능한 코드 (예 : 라이브러리 또는 프레임 워크 코드)의 경우 구현 블록 (옵션 1)에 ivar를 배치하는 경향이 있습니다.


3

구현 위의 개인 인터페이스에 인스턴스 변수를 넣어야합니다. 옵션 3.

이에 대한 문서는 Programming in Objective-C 가이드입니다.

문서에서 :

속성없이 인스턴스 변수를 정의 할 수 있습니다.

값이나 다른 개체를 추적해야 할 때마다 개체의 속성을 사용하는 것이 가장 좋습니다.

속성을 선언하지 않고 고유 한 인스턴스 변수를 정의해야하는 경우 다음과 같이 클래스 인터페이스 또는 구현 상단의 중괄호 안에 추가 할 수 있습니다.


1
동의합니다. ivar를 정의하려는 경우 개인 클래스 확장이 가장 좋습니다. 흥미롭게도 참조하는 문서는 ivar가 정의되어야하는 위치 (단지 세 가지 대안을 모두 배치)에 대한 입장을 취하지 않을뿐만 아니라 더 나아가 ivar를 전혀 사용하지 말 것을 권장하지만 오히려 "속성을 사용하는 것이 가장 좋습니다." 그것이 내가 지금까지 본 최고의 조언입니다.
Rob은

1

공용 ivar는 실제로 @interface에서 선언 된 속성이어야합니다 (1에서 생각하는 것과 같음). 최신 Xcode를 실행하고 최신 런타임 (64 비트 OS X 또는 iOS)을 사용하는 경우 비공개 ivar는 클래스 확장이 아닌 @implementation (2)에서 선언 할 수 있습니다. 3에서 다시 생각합니다.

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