왜 ivar을 사용 하시겠습니까?


153

나는 보통이 질문이 다른 방법으로 질문되는 것을 본다 . (그리고 나는이 Q에 대한 bbum의 대답을 좋아합니다).

내 코드에서 거의 독점적으로 속성을 사용합니다. 그러나 iOS를 오랫동안 개발해온 전통적인 계약자 인 계약자와 협력하고 있습니다. 그는 속성을 거의 선언하지 않고 ivar에 의존하는 코드를 작성합니다. 나는 그가 getter / setter를 거치지 않는 최소한의 성능 향상을 위해 Objective C 2.0 (Oct '07) 및 2)까지 속성이 항상 존재하지 않았기 때문에 그가 사용했기 때문에 이것을 사용한다고 가정합니다.

그가 유출하지 않는 코드를 작성하는 동안 여전히 ivar보다 속성을 사용하는 것이 좋습니다. 우리는 그것에 대해 이야기했고 KVO를 사용하지 않았고 메모리 문제를 처리 한 경험이 있기 때문에 속성을 사용해야 할 이유가 거의 없다고 생각합니다.

내 질문은 더 있습니다 ... 왜 경험이 많은 ivar 기간을 사용하고 싶습니까? ivar 사용이 정당화 될 수있는 성능 차이가 실제로 있습니까?

또한 명확히하기 위해 필요에 따라 세터와 게터를 재정의하고 게터 / 세터 내부의 해당 속성과 관련된 ivar을 사용합니다. 그러나 getter / setter 또는 init 외부에서는 항상 self.myProperty구문을 사용 합니다.


편집 1

좋은 답변을 보내 주셔서 감사합니다. 내가 잘못 생각한 것 중 하나는 ivar을 사용하면 속성이없는 곳에서 캡슐화를 얻는 것입니다. 클래스 연속에서 속성을 정의하십시오. 외부인으로부터 속성이 숨겨집니다. 인터페이스에서 속성을 readonly로 선언하고 구현에서 다음과 같이 readwrite로 재정의 할 수도 있습니다.

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

수업이 계속 진행됩니다.

// readwrite within this file
@property (nonatomic, copy) NSString * name;

그것을 완전히 "비공개"시키려면 클래스 연속에서만 선언하십시오.


2
흥미로운 질문에 대한 찬사를 보내십시오-잘 썼고 또한 Sam의 길을 배우도록 지시받은 것처럼 ivars의 사례를 듣고 싶습니다.
Damo

2
ARC (Automatic Reference Counting)는 속성과 동일한 메모리 관리 이점을 ivar에 적용하므로 ARC 코드에서는 실제로 캡슐화에 대한 차이점이 있습니다.
benzado

1
귀하의 질문과 특히 Edit 1 부분은 실제로 선택된 답변보다 훨씬 유익합니다.
user523234

1
Edit1 : Key-Value-Coding을 사용하여 .h에서 읽기 전용 선언이 하나만있는 경우에도 모든 속성을 읽고 쓸 수 있다고 생각합니다. 예 : [object setValue : [NSNumber numberWithInt : 20] forKey : @ "propertyname "];
Binarian

1
@Sam을 Edit 1로 : 개인 속성을 사용하고 .m 파일에서 클래스 확장 / 연속을 사용하는 경우 하위 클래스에는 표시되지 않습니다. 코드를 다시 작성하거나 클래스 확장자가있는 다른 .h를 사용해야합니다. @ protected / default로 더 쉬워졌습니다.
이진

답변:


100

캡슐화

ivar이 개인용 인 경우 프로그램의 다른 부분은 쉽게 접근 할 수 없습니다. 선언 된 속성을 사용하면 영리한 사람들이 접근자를 통해 쉽게 액세스하고 변경할 수 있습니다.

공연

예, 일부 경우에 차이가있을 수 있습니다. 일부 프로그램은 프로그램의 특정 부분에서 실시간 메시징을 사용할 수없는 제약 조건이 있습니다. 다른 경우에는 속도를 위해 직접 액세스 할 수 있습니다. 다른 경우에는 objc 메시징이 최적화 방화벽 역할을하기 때문입니다. 마지막으로 참조 카운트 작업을 줄이고 최대 메모리 사용을 최소화 할 수 있습니다 (올바르게 수행 된 경우).

사소한 유형

예 : C ++ 유형을 사용하는 경우 직접 액세스가 더 나은 방법 일 수 있습니다. 복사 할 수 없거나 복사하기가 쉽지 않을 수 있습니다.

멀티 스레딩

많은 ivar들이 서로 의존적입니다. 다중 스레드 컨텍스트에서 데이터 무결성을 보장해야합니다. 따라서 중요한 섹션에서 여러 구성원에게 직접 액세스 할 수 있습니다. 상호 의존적 데이터에 대한 접근자를 고수하는 경우 일반적으로 잠금 장치는 재진입해야하며 더 많은 획득 (종종 더 많은 시간이 소요됨)이 종종 발생합니다.

프로그램 정확성

서브 클래스는 모든 메소드를 대체 할 수 있으므로 결국 인터페이스에 쓰는 것과 상태를 적절히 관리하는 것 사이에는 의미 상 차이가있을 수 있습니다. 프로그램 정확성에 대한 직접 액세스는 부분적으로 구성된 상태에서 특히 일반적입니다. 이니셜 라이저 및에서 dealloc직접 액세스를 사용하는 것이 가장 좋습니다. 또한 접근, 편의 생성자의 구현이 일반을 찾을 수 있습니다 copy, mutableCopy및 보관 / 직렬화 구현.

공개 readwrite 접근 자 사고 방식 이있는 모든 것에서 구현 세부 사항 / 데이터를 잘 숨기고있는 것으로 이동함에 따라 더 자주 발생 합니다. 때로는 서브 클래스의 재정의가 올바른 작업을 수행하기 위해 발생할 수있는 부작용을 올바르게 처리해야합니다.

이진 크기

기본적으로 모든 읽기 / 쓰기를 선언하면 프로그램 실행을 고려할 때 일반적으로 필요하지 않은 많은 접근 자 메서드가 생성됩니다. 따라서 프로그램에 약간의 지방과 로딩 시간이 추가됩니다.

복잡성을 최소화

어떤 경우에는 한 가지 방법으로 작성되고 다른 방법으로 읽히는 개인 bool과 같은 간단한 변수에 대해 추가 스캐 폴딩을 추가 + 유형 + 유지하는 것이 완전히 필요하지 않습니다.


속성이나 접근자를 사용하는 것이 나쁘다는 것은 아닙니다. 각각 중요한 이점과 제한 사항이 있습니다. 많은 OO 언어 및 디자인 접근 방식과 마찬가지로 ObjC에서 적절한 가시성을 가진 접근자를 선호해야합니다. 이탈해야 할 때가 있습니다. 따라서 ivar를 선언하는 구현에 대한 직접 액세스를 제한하는 것이 가장 좋습니다 (예 : 선언 @private).


다시 편집 1 :

우리 대부분은 숨겨진 이름을 알고있는 한 숨겨진 접근자를 동적으로 호출하는 방법을 기억했습니다. 한편, 우리 대부분은 한 하지 기억하는 방법 (KVC 이상) 볼 수 없습니다 제대로 액세스 인스턴스 변수에. 클래스 연속이 도움 이되지만 취약점이 생깁니다.

이 해결 방법은 분명합니다.

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

이제 KVC없이 ivar로만 사용해보십시오.


@ 감사합니다, 좋은 질문! 복잡성 : 확실히 두 가지 방식으로 진행됩니다. 다시 캡슐화-업데이트
Justin Justin

@bbum RE : 특이한 예제 나는 그것이 잘못된 해결책이라는 것에 동의하지만, 경험이 많은 objc 개발자들이 그것이 일어나지 않는다고 생각한다고 상상할 수 없다. 나는 다른 프로그램에서 그것을 보았고 App Stores는 개인 Apple API 사용을 금지하는 한까지 갔다.
저스틴

1
object-> foo로 개인 ivar에 액세스 할 수 없습니까? 기억하기 어렵지 않습니다.
Nick Lockwood

1
C-> 구문을 사용하여 객체의 포인터 지연을 사용하여 액세스 할 수 있음을 의미했습니다. Objective-C 클래스는 기본적으로 구조체 아래에 구조체이며 구조체에 대한 포인터가 주어지면 멤버에 액세스하기위한 C 구문은->이며 이는 객관적인 C 클래스의 ivar에서도 작동합니다.
Nick Lockwood

1
ivar이 @private인 경우 @NickLockwood 컴파일러는 클래스 및 인스턴스 메소드 외부에서 멤버 액세스를 금지해야합니다.
저스틴

76

나를 위해 그것은 일반적으로 성능입니다. 객체의 ivar에 액세스하는 것은 그러한 구조체를 포함하는 메모리에 대한 포인터를 사용하여 C의 구조체 멤버에 액세스하는 것만 큼 빠릅니다. 실제로 Objective-C 객체는 기본적으로 동적으로 할당 된 메모리에있는 C 구조체입니다. 이는 일반적으로 코드가 얻을 수있는 속도만큼 빠르며 수동으로 최적화 된 어셈블리 코드도 그보다 빠를 수는 없습니다.

게터 / 설정을 통해 ivar에 액세스하려면 Objective-C 메서드 호출이 필요합니다. Objective-C 메서드 호출은 "일반"C 함수 호출보다 훨씬 느리며 (최소 3-4 배), 심지어 일반 C 함수 호출도 이미 여러 배 느립니다. 구조체 멤버에 액세스 재산의 특성에 따라, 컴파일러에 의해 생성 된 세터 / 게터 구현이 기능에 또 다른 C 함수 호출을 포함 할 수있다 objc_getProperty/ objc_setProperty이는 것으로 retain/ copy/ autorelease와 같은 객체 필요하고 필요한 경우 추가 원자 속성에 대한 spinlocking 수행합니다. 이것은 쉽게 비싸 질 수 있고 나는 50 % 느리다는 것에 대해 이야기하고 있지 않다.

이것을 시도하자 :

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

산출:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

이것은 4.28 배 더 느리며 이것은 원자가 아닌 원시적 int이며, 가장 좋은 경우입니다 . 대부분의 다른 경우는 더 나쁩니다 (원자 NSString *속성을 사용해보십시오 !). 따라서 각 ivar 액세스가 예상보다 4-5 배 느리다는 사실을 알 수 있다면 속성을 사용하는 것이 좋지만 (적어도 성능에 관해서는) 성능이 저하되는 상황이 많이 있습니다. 완전히 받아 들일 수 없습니다.

2015-10-20 업데이트

어떤 사람들은 이것이 실제 문제가 아니라고 주장합니다. 위의 코드는 순전히 합성이며 실제 응용 프로그램에서는 결코 눈치 채지 못할 것입니다. 이제 실제 샘플을 사용해 봅시다.

아래 코드는 Account객체를 정의 합니다. 계정에는 소유자의 이름 ( NSString *), 성별 ( enum) 및 연령 ( unsigned)과 잔액 ( int64_t) 을 설명하는 속성이 있습니다 . 계정 개체에는 init메서드와 compare:메서드가 있습니다. 이 compare:방법은 다음과 같이 정의됩니다. 남성 이전의 여성 주문, 알파벳순으로 된 주문, 이전 이전의 젊은 주문, 잔액 주문이 낮음에서 높음으로 정의됩니다.

사실이 두 계정 클래스가 존재 AccountA하고 AccountB. 구현을 살펴보면 compare:메소드 가 하나만 제외하고 거의 완전히 동일하다는 것을 알 수 있습니다. AccountA개체에 액세스 자신의 특성을 하면서, 방법 (게터)에 의해 AccountB개체에 액세스 자신의 특성을 바르에 의해. 그것은 정말로 유일한 차이점입니다! 그들은 둘 다 getter로 비교하기 위해 다른 객체의 속성에 액세스합니다 (ivar로 액세스하는 것은 안전하지 않습니다! 다른 객체가 하위 클래스이고 getter를 재정의하면 어떻게됩니까?). 또한 ivar로 자신의 속성에 액세스해도 캡슐화가 중단되지 않습니다 (ivar은 여전히 ​​공개되지 않음).

테스트 설정은 정말 간단합니다. 1 개의 Mio 임의 계정을 생성하고 배열에 추가하고 해당 배열을 정렬하십시오. 그게 다야. 물론 두 개의 배열이 있습니다. 하나는 AccountA객체 용이고 다른 하나는 AccountB객체 용이며 두 어레이 모두 동일한 계정 (동일한 데이터 소스)으로 채워져 있습니다. 배열을 정렬하는 데 시간이 얼마나 걸리나요?

어제 내가 한 몇 가지 실행 결과는 다음과 같습니다.

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

보다시피, AccountB객체 배열을 정렬하는 것이 객체 배열을 정렬하는 보다 항상 훨씬 빠릅니다AccountA .

최대 1.32 초의 런타임 차이로 인해 차이가 없다고 주장하는 사람은 UI 프로그래밍을 더 잘 수행해서는 안됩니다. 예를 들어 큰 테이블의 정렬 순서를 변경하려는 경우 이러한 시간 차이로 인해 사용자에게 큰 차이가 생길 수 있습니다 (허용되는 UI와 느린 UI의 차이).

또한이 경우 샘플 코드는 여기서 실제로 수행되는 유일한 작업이지만 코드는 복잡한 시계의 작은 장비입니까? 모든 기어가 이와 같이 전체 프로세스를 느리게하면 결국 전체 시계 속도에 어떤 의미가 있습니까? 특히 하나의 작업 단계가 다른 작업 단계의 결과에 의존하는 경우 모든 비 효율성이 요약됩니다. 대부분의 비 효율성 자체는 문제가 아니며 전체 프로세스에 문제가되는 것은 그 자체입니다. 그리고 그러한 문제는 프로파일 러가 중요한 핫스팟을 찾는 것이기 때문에 프로파일 러가 쉽게 보여줄 수있는 것은 아니지만, 이러한 비 효율성 자체는 핫스팟이 아닙니다. CPU 시간은 평균적으로 그 사이에 분산되어 있지만 각 CPU에는 그 비율이 매우 적으므로 최적화하는 데 총 시간 낭비가 있습니다. 그리고 사실입니다

CPU 시간에 대해 생각하지 않더라도 CPU 시간 낭비가 완전히 허용된다고 생각하기 때문에 "무료"라고 가정하면 전력 소비로 인한 서버 호스팅 비용은 어떻습니까? 모바일 장치의 배터리 런타임은 어떻습니까? 동일한 모바일 앱을 두 번 작성하는 경우 (예 : 자체 모바일 웹 브라우저), 한 번만 모든 클래스가 getter 만 자신의 속성에 액세스하는 버전이고 모든 클래스가 ivar 만 해당 속성에 액세스하는 버전 인 경우 첫 번째 앱을 계속 사용하면 배터리는 기능적으로 동일하지만 사용자에게는 두 번째 배터리를 사용하는 것보다 훨씬 빠릅니다.

다음은 main.m파일 코드입니다 (코드는 ARC를 사용하도록 설정하고 컴파일 할 때 최적화를 사용하여 전체 효과를 확인하십시오).

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end

3
매우 유익하고 철저한 설명. 코드 샘플을위한
공감

1
귀하의 게시물에서 볼 수있는 주요 한정자 중 하나는 "... 중요한 코드 경로에서"입니다. 요점은 코드를보다 쉽게 ​​읽고 쓸 수있게하는 것을 사용하고 중요한 경로라는 것을 최적화하는 것입니다. 필요한 곳에 복잡성을 추가합니다.
Sandy Chapman

1
@ ViktorLexington 내 코드에서 unsigned intARC 사용 여부에 관계없이 보유 / 릴리스되지 않은 것을 설정하고있었습니다 . 유지 / 해제 자체는 비싸므로 유지 관리에서 setter / getter 또는 ivar을 직접 사용하여 항상 존재하는 정적 오버 헤드를 추가 할 때 차이가 줄어 듭니다. 그러나 ivar에 직접 액세스하면 하나의 추가 메소드 호출의 오버 헤드가 여전히 저장됩니다. 초당 수천 번을하지 않는 한 대부분의 경우 큰 문제는 아닙니다. 애플은 init / dealloc 방법을 사용하지 않거나 병목 현상이 발생하지 않는 한 기본적으로 getter / setter를 사용한다고 말합니다.
Mecki

1
@Fogmeister 매우 간단한 실제 예제에서 이것이 얼마나 큰 차이를 만들 수 있는지 보여주는 코드 샘플을 추가했습니다. 그리고이 예제는 수조 건의 계산을 수행하는 수퍼 컴퓨터와는 아무런 관련이 없습니다. 정말 간단한 데이터 테이블 (수백만 개의 앱 중 일반적인 경우)을 정렬하는 것에 관한 것입니다.
Mecki

2
로 표시 @malhal 속성이 copy됩니다 NOT 당신이 그것을 액세스 할 때마다 그 값의 복사본을 만듭니다. 의 게터 copy속성은의 게터처럼 strong/ retain속성입니다. 코드는 기본적으로 return [[self->value retain] autorelease];입니다. 세터 만 값을 복사하고 대략 다음과 같이 표시 [self->value autorelease]; self->value = [newValue copy];되지만 strong/ retain세터는 다음과 같습니다.[self->value autorelease]; self->value = [newValue retain];
Mecki

9

가장 중요한 이유는 정보 숨기기 의 OOP 개념입니다 . 속성을 통해 모든 것을 노출하고 외부 객체가 다른 객체의 내부를 들여다 볼 수 있도록 허용하면 이러한 내부를 사용하므로 구현 변경이 복잡해집니다.

"최소 성능"이득은 빠르게 요약되어 문제가 될 수 있습니다. 나는 경험을 통해 알고 있습니다. 나는 실제로 iDevices를 한계로 가져 오는 앱에서 작업하므로 불필요한 메소드 호출을 피해야합니다 (물론 합리적으로 가능한 경우에만). 이 목표를 돕기 위해 점 구문을 피하는 것이 좋습니다. 예를 들어, 표현식이 self.image.size.width트리거 하는 메소드 호출의 수는 얼마입니까? 대조적으로, 당신은 즉시로 알 수 있습니다 [[self image] size].width.

또한 올바른 ivar 이름을 지정하면 속성없이 KVO가 가능합니다 (IIRC, 저는 KVO 전문가가 아닙니다).


3
+1 "최소 성능"에 대한 적절한 응답은 모든 메소드 호출을 명시 적으로보고 싶어합니다. 속성과 함께 도트 구문을 사용하면 사용자 지정 getter / setter에서 진행되는 많은 작업이 확실히 가려집니다 (특히 getter가 호출 할 때마다 복사본을 반환하는 경우).
Sam

1
세터를 사용하지 않으면 KVO가 작동하지 않습니다. ivar을 직접 변경해도 값이 변경된 관찰자를 호출하지 않습니다!
이진

2
KVC는 ivar에 액세스 할 수 있습니다. KVO는 ivar에 대한 변경 사항을 감지 할 수 없으며 대신 접근자를 호출합니다.
Nikolai Ruhe

9

의미론

  • 무엇을 @property: 인스턴스 변수가 할 수없는 것을 표현할 수 nonatomiccopy.
  • ivar이 표현할 @property수없는 것 :
    • @protected: 서브 클래스에 공개, 외부 개인.
    • @package: 64 비트의 프레임 워크에 공개, 비공개 외부. @public32 비트 와 동일합니다 . Apple의 64 비트 클래스 및 인스턴스 변수 액세스 제어를 참조하십시오 .
    • 한정자. 예를 들어, 강력한 객체 참조의 배열 : id __strong *_objs.

공연

짧은 이야기 : ivar은 빠르지 만 대부분의 용도에는 중요하지 않습니다. nonatomic속성은 잠금을 사용하지 않지만 직접 ivar는 접근 자 호출을 건너 뛰기 때문에 더 빠릅니다. 자세한 내용 은 list.apple.com 에서 다음 이메일 을 읽으십시오 .

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

속성은 많은 방식으로 성능에 영향을줍니다.

  1. 이미 논의한 바와 같이,로드 / 스토어를 수행하는 메시지를 보내는 것은 로드 / 스토어 인라인을 수행하는 것보다 느립니다 .

  2. 로드 / 스토어를 수행하기 위해 메시지를 보내는 것도 i- 캐시에 보관해야하는 코드보다 훨씬 더 많은 코드 입니다. getter / setter가로드 / 스토어 이외의 추가 명령없이 0 개의 추가 명령을 추가 한 경우에도 절반이 확실합니다. 메시지 발신을 설정하고 결과를 처리하기 위해 발신자에 -12 개의 추가 명령이 있습니다.

  3. 메시지를 보내면 해당 선택기에 대한 항목이 메소드 캐시에 유지 되고 해당 메모리는 일반적으로 d- 캐시에서 고정됩니다. 이렇게하면 시작 시간이 늘어나고 앱의 정적 메모리 사용량이 증가하며 컨텍스트 전환이 더욱 어려워집니다. 메서드 캐시는 개체의 동적 클래스에만 적용되므로이 문제로 인해 KVO를 더 많이 사용할 수 있습니다.

  4. 메시지를 보내면 함수의 모든 값이 스택에 쏟아집니다 (또는 다른 시간에 쏟아지는 것을 의미하는 수신자 저장 레지스터에 유지됨).

  5. 메시지를 보내는 것은 임의의 부작용을 가질 수 있으므로

    • 컴파일러가 로컬 메모리가 아닌 메모리에 대한 모든 가정을 재설정하도록합니다.
    • 호이스트, 침몰, 재주문, 합체 또는 제거 할 수 없습니다.

  6. ARC에서 메시지 전송 결과는 +0 리턴에도 수신자 또는 발신자에 의해 항상 유지 됩니다. 메소드가 결과를 유지 / 자동 해제하지 않더라도 호출자는이를 알지 못합니다. 결과가 자동으로 해제되지 않도록 조치를 취하려고합니다. 메시지 전송을 정적으로 분석 할 수 없으므로 제거 할 수 없습니다.

  7. ARC에서, setter 메소드는 일반적으로 +0에서 인수를 취하기 때문에, 위에서 언급 한 것처럼 ARC가 가지고있는 해당 오브젝트의 보유를 ivar로 "전송"할 방법이 없으므로 일반적으로 값을 가져와야 합니다. 두번 유지 / 해제 .

이 중 어느 것도 항상 나쁘다는 것을 의미하지는 않습니다. 속성을 사용해야하는 많은 이유가 있습니다. 다른 많은 언어 기능과 마찬가지로 무료가 아닙니다.


남자.


6

속성 대 인스턴스 변수는 트레이드 오프입니다. 결국 선택은 응용 프로그램에 적용됩니다.

캡슐화 / 정보 숨기기 이것은 디자인 관점, 좁은 인터페이스 및 최소한의 연결에서 Good Thing (TM)으로 소프트웨어를 유지 관리하고 이해할 수있게합니다. Obj-C에서는 아무것도 숨기는 것이 쉽지 않지만 구현 에서 선언 된 인스턴스 변수는 최대한 가깝습니다.

성능 "조기 최적화"는 나쁜 것 (TM)이지만, 최소한 나쁘기 때문에 성능이 좋지 않은 코드를 작성합니다. 로드 나 스토어보다 더 비싼 메소드 호출에 대해서는 논쟁하기가 어렵고, 계산 집약적 인 코드에서는 곧 비용이 추가됩니다.

C #과 같은 속성을 가진 정적 언어에서 컴파일러는 setter / getter에 대한 호출을 최적화 할 수 있습니다. 그러나 Obj-C는 동적이며 이러한 호출을 제거하는 것이 훨씬 어렵습니다.

추상화 Obj-C의 인스턴스 변수에 대한 논쟁은 전통적으로 메모리 관리였습니다. MRC 인스턴스 변수를 사용하면 코드 전체에 유지 / 릴리스 / 오토 릴리즈 호출이 확산되어야하는데, 속성 (합성 여부에 관계없이)은 MRC 코드를 한 곳에 유지합니다. 추상화 원칙은 Good Thing (TM)입니다. 그러나 GC 또는 ARC에서는이 인수가 사라 지므로 메모리 관리에 대한 추상화는 더 이상 인스턴스 변수 대한 인수가 아닙니다 .


5

속성은 변수를 다른 클래스에 노출합니다. 만들고있는 클래스와 관련된 변수 만 필요한 경우 인스턴스 변수를 사용하십시오. RSS 등을 파싱하기위한 XML 클래스는 다수의 델리게이트 메소드 등을 순환합니다. 구문 분석의 각기 다른 패스의 결과를 저장하기 위해 NSMutableString의 인스턴스를 갖는 것이 실용적입니다. 외부 클래스가 해당 문자열에 액세스하거나 조작해야 할 이유가 없습니다. 따라서 헤더 또는 비공개로 선언하고 클래스 전체에서 액세스하십시오. 속성을 설정하면 self.mutableString을 사용하여 getter / setter를 호출하는 데 메모리 문제가 없는지 확인하는 것이 유용 할 수 있습니다.


5

이전 버전과의 호환성 은 나에게 중요한 요소였습니다. 요구 사항의 일부로 Mac OS X 10.3에서 작동해야하는 소프트웨어 및 프린터 드라이버를 개발하고 있었기 때문에 Objective-C 2.0 기능을 사용할 수 없었습니다. 귀하의 질문이 iOS를 대상으로 한 것 같지만 속성을 사용하지 않는 이유를 여전히 공유한다고 생각합니다.

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